背景

最近一直忙于手上澳洲线上项目的整体迁移和升级的准备工作,导致博客和公众号停更。本周终于艰难的完成了任务,借此机会,总结一下项目中遇到的一些问题。

EF Core一直是我们团队中中小型项目常用的ORM框架,在使用SQL Server作为持久化仓储的场景一下,一直表现还中规中矩。但是在本次项目中,项目使用了MySql作为持久化仓储。为了与EF Core集成,团队使用了Pomelo.EntityFrameworkCore.MySql作为EF Core For MySql的扩展。在开发过程中,团队遇到了各种各样在SQL Server场景下没有遇到过的问题,其中最奇怪的,也是隐藏最深的问题,就是将DateTime.Now作为查询条件,产生了非预期的结果。

问题场景

本周在项目升级的过程中,客户反馈了一个问题。

在当前系统的Dashboard页面,有一个消息提醒功能,客户可以自定义一些消息,并且指定提醒的日期。客户遇到的问题是通常添加的消息提醒,在指定日期的上午时间段是不会显示,只有在下午时间段才能看到,比如说客户指定2019年10月26号看到一个的消息提醒,但是在10月26日这天早上8:00-12:00这个时间段,系统总是看不到提醒,只有到了下午的时间段才能看到提醒。

PS:这里客户表达的只是个笼统的问题,但问题确实是上午的大部分时间是看不到消息提醒的,但并不是精确到中午12:00点这个时间, 所以此处不必过于纠结于具体的时间。

查看问题代码

看到这个问题的时候,我自己也很奇怪,难道代码或者数据库使用了时区,导致查询出现了偏差?

于是我就Review了一下此处的查询, 代码如下。

var query = DbContext.CRM_Note_Reminders
.Include(x => x.CRM_Note)
.Where(x => !x.CRM_Note.Is_Deleted
&& !x.Is_Deleted
&& x.Reminder_Date.Date <= DateTime.Now.Date)
.ToList();

PS: 这里可能有同学会有疑问,为啥不用DbFunctions.DiffDays? 原因是DbFunctions.DiffDays是 EF Core for SQLServer的扩展方法,针对MySql还没有官方的实现方案。

从这个查询中,我没有看出任何问题,于是我直接借助一些日志工具,将EF Core生成的查询语句的输出了出来。

其中WHERE条件部分如下:

WHERE (((`x.CRM_Note`.`Is_Deleted` = FALSE)
AND (`x`.`Is_Deleted` = FALSE))
AND (CONVERT(`x`.`Reminder_Date`, date)
<= CONVERT(CURRENT_TIMESTAMP(), date)))

这里CURRENT_TIMESTAMP()是MySql的内置函数,与SQLServer的内置函数GETDATE()不同,CURRENT_TIMESTAMP()默认返回的是UTC时间。因此我们大概能知道,为什么澳洲客户会遇到上面的场景了。

PS: 根据7楼兄弟的反馈,我试了一下,改动Mysql的时区配置之后,果然CURRENT_TIMESTAMP()就改为了对应时区的时间。这里使用UTC时间的原因应该是我在AWS RDS上创建Mysql实例的时候,忽略了时区配置。

由于澳洲处于东10区,与UTC时间有+10个小时的时差,所以当澳洲上午的10点之前,UTC时间都是在当前澳洲日期的前一天,所以系统中出现了当天的消息提醒在上午时间段不能正常显示的问题。

PS: 由于澳洲是分冬令时和夏令时的,夏令时时间要加一个小时,所以实际上客户在每天的11点之前都无法看到正确的消息提醒。

深入思考

你这可能会非常奇怪,为什么DateTime.Now会被转化成内置函数CURRENT_TIMESTAMP(),而没有使用我们传入的值DateTime.Now.Date呢?

其实EF/EF Core在查询是时候是分2个阶段的,一个是组合查询表达式树的阶段,一个是真正的查询阶段。

在组合查询表达式树的阶段,EF/EF Core只会去组合表达式,而不会去尝试计算表达式的值,所以这个阶段DateTime.Now.Date的值并没有被计算出来, 在进入正常查询阶段的时候, EF/EF Core会尝试将查询表达式树翻译成SQL脚本,这时候由于我们的EF ProviderMySql Provider, 恰巧DateTime.Now可以翻译成Mysql的内置函数CURRENT_TIMESTAMP(), 所以这里EF/EF Core就跳过了表达式值的计算,直接将其翻译成了对应的内置函数,所以导致生成的SQL查询和我们的预期有偏差。

那么我们该如何解决这个问题呢?

解决方案

经过了以上的思考,其实解决这个问题也就很简单了,我们可以将DateTime.Now.Date先计算出来,保存在一个变量中,然后将这个变量传入查询中。

var today = DateTime.Now.Date;

var query = DbContext.CRM_Note_Reminders
.Include(x => x.CRM_Note)
.Where(x => !x.CRM_Note.Is_Deleted
&& !x.Is_Deleted
&& x.Reminder_Date.Date <= today)
.ToList();

由此生成的MySQL脚本如下:

WHERE (((`x.CRM_Note`.`Is_Deleted` = FALSE)
AND (`x`.`Is_Deleted` = FALSE))
AND (CONVERT(`x`.`Reminder_Date`, date) <= @__date_0))

这样我们就得到了一个正确的结果,澳洲客户也就收到了正确的消息。

是不是有种差之毫厘,谬以千里的感觉呢?

Entity Framework Core For MySql查询中使用DateTime.Now的问题的更多相关文章

  1. Entity Framework Core 实现MySQL 的TimeStamp/RowVersion 并发控制

    将通用的序列号生成器库 从SQL Server迁移到Mysql 遇到的一个问题,就是TimeStamp/RowVersion并发控制类型在非Microsoft SQL Server数据库中的实现.SQ ...

  2. Entity Framework Core生成的存储过程在MySQL中需要进行处理及PMC中的常用命令

    在使用Entity Framework Core生成MySQL数据库脚本,对于生成的存储过程,在执行的过程中出现错误,需要在存储过程前面添加 delimiter // 附:可以使用Visual Stu ...

  3. Entity Framework Core今日所得:避免 IEnumerable 以及 IQueryable 陷阱

    避免 IEnumerable 以及 IQueryable 陷阱: IEnumerable示用Linq会先去数据库查询所有记录,然后再条件查询. IQueryable接口派生自IEnumerable,但 ...

  4. 在Apworks数据服务中使用基于Entity Framework Core的仓储(Repository)实现

    <在ASP.NET Core中使用Apworks快速开发数据服务>一文中,我介绍了如何使用Apworks框架的数据服务来快速构建用于查询和管理数据模型的RESTful API,通过该文的介 ...

  5. 创建ASP.NET Core MVC应用程序(3)-基于Entity Framework Core(Code First)创建MySQL数据库表

    创建ASP.NET Core MVC应用程序(3)-基于Entity Framework Core(Code First)创建MySQL数据库表 创建数据模型类(POCO类) 在Models文件夹下添 ...

  6. 全球首发免费的MySql for Entity Framework Core

    from:http://www.1234.sh/post/pomelo-data-mysql?utm_source=tuicool&utm_medium=referral Source 源代码 ...

  7. 关于MySql entity framework 6 执行like查询问题解决方案

    原文:关于MySql entity framework 6 执行like查询问题解决方案 本人不善于言辞,直接开门见山 环境:EF6.0.0.0+MySQL Server5.6+MySqlConnec ...

  8. Entity Framework Core 软删除与查询过滤器

    本文翻译自<Entity Framework Core: Soft Delete using Query Filters>,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 注意 ...

  9. Entity Framework Core 2.0 中使用LIKE 操作符

    Entity Framework Core 2.0 中使用LIKE 操作符 不定时更新翻译系列,此系列更新毫无时间规律,文笔菜翻译菜求各位看官老爷们轻喷,如觉得我翻译有问题请挪步原博客地址 本博文翻译 ...

随机推荐

  1. Vue学习系列(三)——基本指令

    前言 在上一篇中,我们已经对组件有了更加进一步的认识,从组件的创建构造器到组件的组成,进而到组件的使用,.从组件的基本使用.组件属性,以及自定义事件实现父子通讯和巧妙运用插槽slot分发内容,进一步的 ...

  2. Rancher 2.3.2 Stable!Istio UI已经GA!生产可用!

    2019年10月9日,Rancher 2.3正式发布,这是Rancher Labs迄今为止最重要的产品版本.Rancher 2.3是业界首个GA支持Windows容器的Kubernetes管理平台,并 ...

  3. 闪讯 开启wifi教程

    这是我自己试了几次之后发现的,也不是什么技术活. 首先说下,我的比较是小米pro笔记本,一般笔记本都是自带wifi功能的.如果要开wifi的话,必须是用网线连接才可以,通过wifi连接网络就不能开移动 ...

  4. python中根据时间获取周数,通过周数获取时间

    # 时间## 时间和周数 import time import datetime # 获取今天是第几周 print(time.strftime('%W')) # 获取当前是周几(0-6,0代表周一) ...

  5. 简要概括java技术体系

    以前一直在学java程序设计语言,学完了就以为自己已经把java学得差不多了,直到最近在看一本书<深入理解java虚拟机>,才发现自己以前学的只不过是冰山一角.相信很多小伙伴跟我一样,在没 ...

  6. scrapy爬取京东iPhone11评论(一)

    咨询行业中经常接触到文本类信息,无论是分词做词云图,还是整理编码分析用,都非常具有价值. 本文将记录使用scrapy框架爬取京东IPhone11评论的过程,由于一边学习一边实践,更新稍慢请见谅. 1. ...

  7. 阿里巴巴开源项目: 基于mysql数据库binlog的增量订阅&消费

    背景 早期,阿里巴巴B2B公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求.不过早期的数据库同步业务,主要是基于trigger的方式获取增量变更,不过从2010年开始,阿里系公司开始逐步的尝 ...

  8. (十二)golang--进制和位运算

    1.基本进制 (1)二进制:0,1,满2进1 在golang中,不能直接使用一个二进制表示一个整数,可以用八进制.十进制和十六进制表示 (2)十进制:0-9,满10进1 (3)八进制:0-7,满8进1 ...

  9. python购物车练习题

    # 购物车练习# 1.启动程序后,让用户输入工资,打印商品列表# 2.允许用户根据商品编号购买商品# 3.用户选择商品后,检测余额是否够,够就直接扣款,不够就提醒# 4.可随时退出,退出时,打印已购买 ...

  10. Linux\centos 配置阿里云源

    # Aliyun 源配置CentOS1.备份mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup2 ...