背景

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

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. 玩转PubSubClient MQTT库

    1.前言     在ESP8266学习系列中,博主一直使用HTTP协议.HTTP连接属于短连接,而在物联网应用中,广泛应用的却是MQTT协议.所以,本篇我们将学习Arduino平台上的MQTT实现库 ...

  2. 关于M23内核简介 - 待续

    1.定位 M23是基于最新的ARMv8-M构架的主要关注低功耗应用的微控制器,未来会是M0.M0+的替代品. M33是基于最新的ARMv8-M构架的主要关注高能效应用的微控制器,未来会替换M3.M4. ...

  3. Webpack打包css后z-index被重新计算的解决方法

    发现问题 最近在使用 Webpack 打包 css 文件时,发现了一个问题,发现打包后的 z-index 值跟源文件 z-index 不一致. 如下图,左侧是源文件,右侧是打包后的文件: 即使加上 ! ...

  4. angularjs通过ng-bind-html指令和$sce服务绑定html

    代码: <!doctype html> <html lang="en"> <head> <meta charset="UTF-8 ...

  5. docker-compose下的java应用启动顺序两部曲之二:实战

    上篇回顾 本文是<docker-compose下的java应用启动顺序两部曲>的终篇,在上一篇<docker-compose下的java应用启动顺序两部曲之一:问题分析>中,我 ...

  6. 透明度设置opacity

    透明度设置opacity属性 示例 <!DOCTYPE html> <html> <head> <style> div { background-col ...

  7. SpringCloud之Feign和Ribbon的选择(五)

    Ribbon Ribbon 是一个基于 HTTP 和 TCP 客户端的负载均衡器它可以在客户端配置 ribbonServerList(服务端列表),然后轮询请求以实现均衡负载它在联合 Eureka 使 ...

  8. 卷积神经网络详细讲解 及 Tensorflow实现

    [附上个人git完整代码地址:https://github.com/Liuyubao/Tensorflow-CNN] [如有疑问,更进一步交流请留言或联系微信:523331232] Reference ...

  9. plsql + instantclient 连接oracle ( 超简单)

    1.instantclient 下载并解压 instantclient 下载地址 https://www.oracle.com/technetwork/cn/database/features/ins ...

  10. 一 :Mysql基础

    1.Mysql初识 三高角度引入 三高:高并发,高可用,高性能. why: 1.1数据文件和程序不应该放在同一台机器上!一旦机器挂了,数据和程序全都挂了不能对外提供服务,高可用性差. 1.2纵向扩展有 ...