📚

EntityFramework (モデルファースト)+SQL Server で日付型を扱う時の罠

2021/08/11に公開

環境

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