在一個訪問下位機的程序中,返回的時間戳有時候因斷線產生0001年01月01日的時間,而原先使用拼接SQL進行數據存儲的操作時,這個問題是可以跳過的。
這次把拼接SQL的部分重新改為EF進行管理,這個坑就不能避免了。
datetime2是個什么鬼?
datetime2 是一個在范圍和精度上都優於 datetime 類型的時間類型。
傳統的datetime類型,時間的最小值是 1753-01-01,精度最多到秒后三位。這也是被大家所熟知的時間類型。在今天的多數業務下,這個時間在使用上沒有什么問題。
但是總有些不那么和諧的玩藝,居然總想搞個大新聞,用時間類型來標志異常(其實也沒什么問題,畢竟異常了未必有時間戳,那么空的時間戳就出現了)
於是,時間的Null怎么表示呢?多數的策略選擇了 0001-01-01 00:00:00這樣一個時間。畢竟比起大家都知道的 1753-01-01,這個公元元年第一天的時間更具有標志意義。
但是傳統那個Datetime它記不了這個值啊。
所以,一個更長、更細(好像哪里不對的樣子)的datetime2類型出現了。 這個類型其實也基本被各個語言都實現了,所以算是一個標准。只是大多數程序員在很長的職業生涯里未必真的遇到這種情況,所以並不被多數人熟知。
那么,報錯的實際情況是什么呢?
System.Data.SqlClient.SqlException: 從 datetime2 數據類型到 datetime 數據類型的轉換產生一個超出范圍的值.
這個其實有點誤導性質。
這個轉換實質上是EF內部完成的。在EF創建了DB First的Model后,正常情況下,相關的這個映射字段的類型依然和數據庫設計保持一致,是datetime。 但是,在賦值的過程中,如果這個字段沒有賦值、或者賦值給出了0001-01-01的時間(或者小於1753的時間),結果就是EF會根據表的設計約束(顯然就是字段必填了)轉換成一個實質上的datetime2類型。(確實這個類型沒辦法自己聲明出來啊,所以這應該算一個坑)
然后,接下來的故事就必然發生了,在EF執行 db.SaveChanges()操作時,數據庫只接受datetime類型的日期,而不接受datetime2類型的值。進而就拋出這樣一個錯誤。
so?解決方案呢?
顯然,最直接的解決策略是改數據表的字段為 datetime2類型。 這個是支持的,所以沒有什么大問題。當然后果就是,如果進行一個按時間正序進行的查詢,那么肯定會有大量的0001-01-01的數據出現。在多數業務系統中,這種數據應該直接判定為錯數而丟棄。
稍好的方案是對代碼中的datetime設置為 Nullable 類型,允許空。 相比之下,這個做法更優雅一些,程序的邏輯更直觀。
當然,也有一些算是奇淫技巧的方案,酌情吧:
- 在C#中用new DateTime(year,month,day,hour,minute,second)來限制精度
- 這個方法不太推薦,將model的edmx中的providerManifestToken設置成2005,這樣ef就默認轉化成datetime
個人覺得,這倆方案並不和諧。其一,需要在時間類型上作不少限制,其二,這樣修改配置的思路基本上是只解決當前問題,不顧其他影響的思路。
在我的實際案例里,因為這個值需要作為標記異常的用途,所以在不修改前一個版本的底層的要求下,暫時給設置成1753-01-01確保兼容,在全新部署的版本上再進一步利用這個標志。