背景

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

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. CocosCreator中_worldMatrix到底是什么(下)

    Cocos Creator 中 _worldMatrix 到底是什么(下) 1. 摘要 上篇介绍了矩阵的基本知识以及对应图形变换矩阵推倒.中篇具体介介绍了对应矩阵转换成cocos creator代码的 ...

  2. Exception evaluating SpringEL expression:

    Exception evaluating SpringEL expression:错误 说明: 在帮助同事看BUG的时候遇上了这个问题,不知道是前端还是后端的错误 在网上找了很多文章解决的方法,很多都 ...

  3. 百万年薪python之路 -- 面向对象之继承

    面向对象之继承 1.什么是面向对象的继承 继承(英语:inheritance)是面向对象软件技术当中的一个概念. 通俗易懂的理解是:子承父业,合法继承家产 专业的理解是:子类可以完全使用父类的方法和属 ...

  4. C# 中yield关键字解析

    前言 前段时间了解到yield关键字,一直觉得还不错.今天给大家分享一下yield关键字的用法.yield return 返回集合不是一次性返回所有集合元素,而是一次调用返回一个元素.具体如何使用yi ...

  5. java编写基于netty的RPC框架

    一 简单概念 RPC:(Remote Procedure Call),远程调用过程,是通过网络调用远程计算机的进程中某个方法,从而获取到想要的数据,过程如同调用本地的方法一样. 阻塞IO:当阻塞I/O ...

  6. 日天老师的django相关博客

    Yuan先生的博客网址 1 Web应用 https://www.cnblogs.com/yuanchenqi/articles/8869302.html 2 http协议 https://www.cn ...

  7. 用Python新建用户并产生随机密码

    说明:本次代码是在Linux下执行的,windows也可以用,把添加用户密码的命令改成windows的就ok了 用Python新建用户并产生随机密码 import passwd_name as pn ...

  8. Leetcode Tags(13)Bit Manipulation

    一.477.汉明距离总和 输入: , , 输出: 解释: 在二进制表示中,4表示为0100,14表示为1110,2表示为0010.(这样表示是为了体现后四位之间关系) HammingDistance( ...

  9. APP打包设置程序版本号

    正确设置方式是: 注意,以下修改不会起作用<manifestxmlns:android="http://schemas.android.com/apk/res/android" ...

  10. Android原生PDF功能实现

    1.背景 近期,公司希望实现安卓原生端的PDF功能,要求:高效.实用. 经过两天的调研.编码,实现了一个简单Demo,如上图所示. 关于安卓原生端的PDF功能实现,技术点还是很多的,为了咱们安卓开发的 ...