背景

          最近一直忙于手上澳洲線上項目的整體遷移和升級的準備工作,導(dǎo)致博客和公眾號停更。本周終于艱難的完成了任務(wù),借此機會,總結(jié)一下項目中遇到的一些問題。

          EF Core一直是我們團隊中中小型項目常用的ORM框架,在使用SQL
          Server作為持久化倉儲的場景一下,一直表現(xiàn)還中規(guī)中矩。但是在本次項目中,項目使用了MySql作為持久化倉儲。為了與EF Core集成,團隊使用了
          Pomelo.EntityFrameworkCore.MySql作為EF Core For MySql的擴展。在開發(fā)過程中,團隊遇到了各種各樣在SQL
          Server場景下沒有遇到過的問題,其中最奇怪的,也是隱藏最深的問題,就是將DateTime.Now作為查詢條件,產(chǎn)生了非預(yù)期的結(jié)果。

          問題場景

          本周在項目升級的過程中,客戶反饋了一個問題。


          在當前系統(tǒng)的Dashboard頁面,有一個消息提醒功能,客戶可以自定義一些消息,并且指定提醒的日期??蛻粲龅降膯栴}是通常添加的消息提醒,在指定日期的上午時間段是不會顯示,只有在下午時間段才能看到,比如說客戶指定2019年10月26號看到一個的消息提醒,但是在10月26日這天早上8:00-12:00這個時間段,系統(tǒng)總是看不到提醒,只有到了下午的時間段才能看到提醒。

          PS:這里客戶表達的只是個籠統(tǒng)的問題,但問題確實是上午的大部分時間是看不到消息提醒的,但并不是精確到中午12:00點這個時間,
          所以此處不必過于糾結(jié)于具體的時間。

          查看問題代碼

          看到這個問題的時候,我自己也很奇怪,難道代碼或者數(shù)據(jù)庫使用了時區(qū),導(dǎo)致查詢出現(xiàn)了偏差?

          于是我就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還沒有官方的實現(xiàn)方案。

          從這個查詢中,我沒有看出任何問題,于是我直接借助一些日志工具,將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的內(nèi)置函數(shù),與SQLServer的內(nèi)置函數(shù)GETDATE()不同,CURRENT_TIMESTAMP()
          默認返回的是UTC時間。因此我們大概能知道,為什么澳洲客戶會遇到上面的場景了。

          PS: 根據(jù)7樓兄弟的反饋,我試了一下,改動Mysql的時區(qū)配置之后,果然CURRENT_TIMESTAMP()
          就改為了對應(yīng)時區(qū)的時間。這里使用UTC時間的原因應(yīng)該是我在AWS RDS上創(chuàng)建Mysql實例的時候,忽略了時區(qū)配置。


          由于澳洲處于東10區(qū),與UTC時間有+10個小時的時差,所以當澳洲上午的10點之前,UTC時間都是在當前澳洲日期的前一天,所以系統(tǒng)中出現(xiàn)了當天的消息提醒在上午時間段不能正常顯示的問題。

          PS: 由于澳洲是分冬令時和夏令時的,夏令時時間要加一個小時,所以實際上客戶在每天的11點之前都無法看到正確的消息提醒。

          深入思考

          你這可能會非常奇怪,為什么DateTime.Now會被轉(zhuǎn)化成內(nèi)置函數(shù)CURRENT_TIMESTAMP(),而沒有使用我們傳入的值
          DateTime.Now.Date呢?

          其實EF/EF Core在查詢是時候是分2個階段的,一個是組合查詢表達式樹的階段,一個是真正的查詢階段。



          在組合查詢表達式樹的階段,EF/EF Core只會去組合表達式,而不會去嘗試計算表達式的值,所以這個階段DateTime.Now.Date
          的值并沒有被計算出來, 在進入正常查詢階段的時候, EF/EF Core會嘗試將查詢表達式樹翻譯成SQL腳本,這時候由于我們的EF Provider是
          MySql Provider, 恰巧DateTime.Now可以翻譯成Mysql的內(nèi)置函數(shù)CURRENT_TIMESTAMP(), 所以這里EF/EF
          Core就跳過了表達式值的計算,直接將其翻譯成了對應(yīng)的內(nèi)置函數(shù),所以導(dǎo)致生成的SQL查詢和我們的預(yù)期有偏差。

          那么我們該如何解決這個問題呢?

          解決方案

          經(jīng)過了以上的思考,其實解決這個問題也就很簡單了,我們可以將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))
          這樣我們就得到了一個正確的結(jié)果,澳洲客戶也就收到了正確的消息。

          是不是有種差之毫厘,謬以千里的感覺呢?

          友情鏈接
          ioDraw流程圖
          API參考文檔
          OK工具箱
          云服務(wù)器優(yōu)惠
          阿里云優(yōu)惠券
          騰訊云優(yōu)惠券
          京東云優(yōu)惠券
          站點信息
          問題反饋
          郵箱:[email protected]
          QQ群:637538335
          關(guān)注微信

                成人片色情免费观看网站一级 | 国产男人猛躁进女人的导演 | 我x你xx网在线看 | 毛片手机在线免费观看 | 色婷婷久久综合久色 |