2014/10/23

解決 .Net 日期與 SQL 日期的匹配問題

雖然這是一個陳年的老問題, 我在實際應用中卻從來沒有給我帶來麻煩 -- 直到今天。當然, 這也是一個陷阱; 如果我不是以前就知道有這個問題, 我恐怕會百思不得其解。

這個問題很簡單, 相信許多人都有經驗: 把一個日期欄位寫進資料庫, 再取出來時, 發現兩個日期並不相等。它的陷阱在於, 這兩個日期可能有完全相同的年、月、日、時、分、秒, 但是寫進去 SQL 之後, 再讀出來, 它的 millisecond 值卻不同。Ticks 也不同。這個問題使得我的單元測試始終過不了

有人說這是因為 .Net 和 SQL 在日期物件中使用的數值精度不同。另外, 有人建議改用 SQL 2008 之後的 datetime2 欄位, 而不要使用 datetime 欄位。不過我試過幾次之後, 我發現使用 datetime2 並不能解決問題; 我仍然必須採用其它解法, 才能解決。

解決的方法是什麼呢? 就是不要使用 DateTime 的 == 運算子, 也不要使用 DateTime.Equals() 方法。我寫了一個擴充方法, 用它來取代前兩者:

public static bool IsNearTo(this DateTime t1, DateTime t2)
{
    return Math.Abs(t1.Ticks - t2.Ticks) < 300000;
}

根據 MSDN 上的說明, SQL datetime 欄位和 .Net DateTime 的精確度誤差可能有 0.002 秒之多。我保守一點, 用 0.003 秒來做判斷, 那麼如果兩個時間的差異不到 30 毫秒 (300000 Ticks), 就算兩個時間相等。不過, 我在實際測試時, 設定為 30 毫秒仍偶有錯誤。事實上, 若根據 datetime2 的說明文件中, 它指出此型別的精確度為 100 毫秒, 意思就是誤差最多為 100 毫秒, 所以我們最大可以把上述程式中的值改成更保守的 1000000 (100 毫秒), 如此應該可以確保萬無一失。

套用上述程式, 使用 this.DateStarted.IsNearTo(other.DateStarted) 這種寫法, 就可以避免出現上述的問題了。當然, 如果讀者的使用情境不同 (例如日期時間是從 SQL 產生, 而不是你的 .Net 程式), 那麼最好不用使用這種做法 (也沒意義)。

沒有留言:

張貼留言