EntityFramework (モデルファースト)+SQL Server で日付型を扱う時の罠
環境
Visual Studio 2015 (VB)
EntityFramework 6.1.2
SQL Server 2012 Express LocalDB
はじめに
Entity Framework を使ったモデルファーストの開発をすると、
ER 図を書くだけで
- 自動的にエンティティクラスを定義してくれる
- 自動的にテーブル生成 SQL 文を作ってくれる
など、便利です。
罠
しかし、日付型に関しては罠があります。
エンティティクラスでは、日付型の列は DateTime 型 にマップされます。(コード上は Date 型ですが、Date 型 = DateTime 型 です)
SQL Server のテーブルでは、日付型の列は datetime 型 として生成されます。
一見、何の問題もなさそうです。型の名前も同じだし。
ここに罠があります。
.Net の DateTime 型と同等以上の精度を持つ SQL Server の型は <em>datetime2 なのです。
DateTime 型の精度 2016/03/24 12:34:56.1234567
datetime 型の精度 2016/03/24 12:34:56.123
なので、DB に値を保存するとき、後ろの 4567 は丸められてしまいます。
何が問題?
EntityFramework を使って、日付型のデータを入れて、その値で検索をかけると
ヒットしません。擬似コードで示すと以下のようなかんじです。
dim dt = Now
dim entity = DbContext.TestTable.create()
entity.dateTimeColumn = dt
DbContext.save() ' これでINSERT文が発行されるが、日付は丸められる
dim entity2 = (From e In DbContext.TestTable
Where e.dateTimeColumn = dt).SingleOrDefault
' entity2は取得できていない
entity2 を取得する為の LINQ to SQL が Where 句 のパラメタを設定するとき、
datetime2 でパラメタをセットするので、値が異なるとしてヒットしません。
対策
秒以下を 0 にしてしまえば、問題無く処理されます。
Dim dtStr = Now.ToString("yyyy/MM/dd HH:mm:ss")
dim dt = Date.Parse(dt)
' dt.Milliseconds = 0 とはできない。残念ながらReadOnly
所感
思ったのは、Visual Studio のモデルデザイナ側で datetime2 型でテーブルを
生成する SQL を出力できれば良いのですが、モデルデザイナ上で選べる型は datetime
しか存在しません。
LINQ to SQL(の SQL Server 向け実装)は DateTime 型を datetime2 として扱うので、
ちょっとミスマッチが起きてしまっています。今回は SQL Server の話なのですが、
Oracle 等でも同様の罠が発生する可能性がありますのでご注意を。という話でした。
Discussion