Entity Frameworkでデータベースで生成される値に対応する

Entity Framework (EF)でデータベース側で値を生成したいとき、
思った通りに動かないなんて経験はないでしょうか。

例えば、IDの自動採番や、更新日の更新です。

自動更新を設定したのに、DBは無反応なんていうことも実際に起こります。

そこで今回は、Entity Framework の設定がいつ機能するのかと、なぜ動かないのかに注目して対策を紹介していきます。

Entity Framework を用いて自動生成するときに、
C#で設定できること、できないこと(=DBでやること)がわかる内容になっています。

自動採番・自動更新日時に興味のある方は是非ご覧ください。

Entity Framework で自動更新クエリが流れるのはマイグレーションだけ

Entity Framework がデータベースに働きかけるタイミングは
マイグレーションとプログラム実行時の2回です。

しかし、自動更新の設定を行うのはマイグレーションだけになります。

  • Entity Framework の自動更新はDMLではなくDDLが担う
  • マイグレーションでCreate Tableなどのクエリが作られる
  • プログラムが稼働するタイミングは自動生成値を受け取るだけ
  • マイグレーションと実環境で異なる機能に注意

理由を一つずつ見ていきましょう。

Entity Framework の自動更新はDMLではなくDDLが担う

データベース上の値を利用して、データを登録するタイミングは2つあります。

ひとつは、DMLであるSelect,Update,Delete,Insertなどの表に問い合わせるクエリを使うタイミングです。

もうひとつは、DDLであるCreate Table,AlterTable,Create Triggerなど、
表そのものを作成するタイミングです。

Entity Framework で自動採番・自動更新を構築するはDDL
つまりテーブルを作成したりトリガーを作ることが基本になります。

マイグレーションでCreate Tableなどのクエリが作られる

マイグレーションとは、C#のコードからデータベースを生成する機能のことです。

まだデータベースやテーブルの設計が固まっていないときに、
作っては壊しを繰り返しすることで、コードやテーブル構造を洗練していく
いわゆるアジャイル的な開発をするときに利用される仕組みです。

属性や、DbContextで定義された内容にしたがって、
データベースを生成・変更することができる機能です。

このマイグレーションで自動採番や自動日時更新が作られます。

プログラムが稼働するタイミングは自動生成値を受け取るだけ

このタイミングでは自動採番の設定が終わったタイミングになります。
実際にプログラムを動かす段階ではSelect文、Update文が利用されるわけですが、その際に、


のようなクエリは発行されません。

プログラムが稼働するタイミングでやることは、
自動採番された値をDBから受け取りモデルに反映するだけです。

マイグレーションと実環境で異なる機能に注意

思った通りに動かない原因は、マイグレーションの機能なのか、
実際に プログラム で動かすときに使われる機能が混在しているからです。

この前提を間違えると、実稼働のときは想定したクエリを発行しないということになります。

マイグレーションと実稼働のときに発行されるクエリの違い

マイグレーション時に自動更新する仕組みが構築されることを実際の例で見ていきましょう。

今回はSQL Serverをデータベースとして使います。

Entity Framework をつかったコードをみてから
マイグレーション時に発行されるクエリ、コードから発行されるクエリを見ます。

  • 自動採番・自動日時更新を設定するコード
  • マイグレーション時に発行されるクエリ
  • 実行時に発行されるクエリ
  • プログラム実行時に自動更新クエリを生成するわけではない

自動採番・自動日時更新を設定するコード

モデルで自動更新の属性を設定する


今回用意したモデルは、Nameプロパティを除いた他のプロパティすべてで、
データベース側で生成された値を反映するように属性を付与しました。

自動生成する値は、Idは採番、CreateAtは作成日時、UpdateAtは更新日時とします。

[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
はIdを+1ずつデータベース側で増やすための仕組みです。

CreateAtとUpdateAtは [DatabaseGenerated(DatabaseGeneratedOption.Computed)]でマークしました。

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
はidのように+1ずつ増やす以外で自動生成値を、自由に設定するための仕組みです。

ちなみに、Computed属性でどのように計算するかは、この属性付与だけでは決めることができません。

次のDbContextで行います。

DbContextで自動更新の追加設定

属性で設定しきれないものはDbContextのOnModelCreateingメソッドで行っていきます。


先ほどのモデルで設定した、

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]

に対応する自動生成の設定を行っていきます。

まずはInsert時に作成日時をデータベース側で作るために

HasDefaultValueSqlメソッドで
デフォルト値を生成するクエリを設定しました。

UpdateAtはinsert時に現在時刻は入るものの、この時点でもまだ、Update時の自動更新は設定完了していません。

なお、Idは属性指定だけで完了していますので、この場では何もコードを加えません。

さらにMigrationクラスのUpメソッドでトリガーを追加

マイグレーション時にトリガーを仕込むことで完了します。

トリガーはマイグレーションコードとしては生成されないので、
自前で追加していきます。

今回のトリガーコードはこちら


この時点でお察しのかたもいらっしゃるかもしれませんが、
柔軟なデータ更新は、属性などではなく、結局はクエリで書いていく必要があるということです。

マイグレーション時に発行されるクエリ

マイグレーションを実行すると画像のようなテーブルが出来上がります。

その際に発行されたクエリをみてみましょう。
なお、ログにクエリを吐き出す方法は以下のリンクからご確認ください。

>>発行クエリをログとしてみる方法はこちら


テーブルを作成するCreate文が発行され、追加で
自前で用意したトリガー設定クエリもここで実行されます。

更新ごとにUpdateAtに現在時刻が入いるトリガーが無事作成されたことになります。

では他の自動設定について細かく見ていきましょう。

IdカラムにはIdentityが付与されてインクリメントも設定されました。

作成日時のCreateAtにも、指定したgetdate関数が機能するように設定されました。

  • EFでは自動採番・自動日時更新の設定はマイグレーションで行う
  • EFではCreate文でテーブルやトリガーを作成することで、自動採番・自動日時更新を設定できる

これまで確認してきたとおり、自動採番・自動日時更新をデータベース側で行うのは、
マイグレーションの時
ということがクエリ結果と作られたテーブルからわかりましたね。

では、実行に発行されるクエリは何をしているのでしょうか。

実行時に発行されるクエリ

実際にプログラムを実行するときに、Entity Framework から発行されるクエリを見てみましょう。

特に属性による違いに注目します。

insert時はDatabaseGeneratedOption.IdentityとComputedの両方が取得される

まずはinsert文です。名前を指定して登録してみましょう。実行コードはこちら。


発行されるクエリはこちら。


注目すべきはInsert文のカラムの数と、その後Select文が発行されていることです。


DatabaseGeneratedOption.IdentityとComputedで指定されたプロパティを
Select文でデータベースで生成される値を取得してきていますね。

一方で発行されたInsert文には、GetDate()やIdを+1をする記述はありません。
Nameプロパティをinsertしているだけです。

前段で説明した通り、プログラムを実行する前の段階で自動採番・自動生成する仕組みは完成しているためです。

では、IdentityとComputedの属性の役割とはなにか?

それは、Insert文に属性を付与した列を除外する役割と、
データベースで生成された値をSelect文でモデルに反映する役割です。

update時はComputeでマークした列が取得される

続いてUpdate文です。
名前をアップデートしてみましょう。実行コードはこちら。


発行されるクエリはこちら。


先ほどと同じように、まずはUpdate文のカラムの数を見てみましょう。

insert文と同様に更新はnameだけであり、Update文ではCreateAtやUpdateAtを更新してはいませんね。

続いてSelect文の部分です。IdentityとComputedの違いが出てきます。

Update文の場合は、プライマリーキーは変わっていないので、
Select文で取得する列がComputedで指定したCraeatAtとUpdateAtだけになりました。

update文では列を除外する役割は、IdentityとComputedで変わらないけれども、
データベースで生成された値をSelect文でモデルに反映するのはComputedだけです。

プログラム実行時に自動更新クエリを生成するわけではない

一旦まとめます。

  • EFではinsert,update文などでは現在時刻の設定であったり、Idの採番を行わない
  • IdentityとComputedはinsert,update文からカラムを除外する機能をもつ
  • IdentityとComputedは更新後にDBで生成された値をSelectする機能をもつ

改めてになりますが、プログラム実行前に自動採番・自動更新の仕組みを構築する必要があります。

自動採番・自動日時更新はプログラムを動かす前に行う

これまで、Entity Framework の機能を紹介してきたとおり、
つまるところデータベースに自動採番や自動日時更新を行うためのクエリをプログラム実行前に行う必要がありました。

つまり、すでにデータベースがある場合は、いくら属性付与やDbContext内で設定しても、
自動採番・自動日時更新されることはありません。

自動採番・自動日時更新を行うには、

Entity Framework のマイグレーション機能でデータベースを構築するのか、
マイグレーションを介さず、クエリやSSMSなどGUIで構築する必要があります。

まとめ

それでは最後にまとめます。

  • EFでは自動採番・自動日時更新の設定はマイグレーションで行う
  • EFではCreate文でテーブルやトリガーを作成することで、自動採番・自動日時更新を設定できる
  • EFではinsert,update文などでは現在時刻の設定であったり、Idの採番を行わない
  • IdentityとComputedはinsert,update文からカラムを除外する機能をもつ
  • IdentityとComputedは更新後にDBで生成された値をSelectする機能をもつ

IDやレコードの作成日時と更新日時など、Entity Frameworkがどのように関わっていくのかを紹介しました。

データベース側で生成すべきものを、プログラム側で設定してしまうと、

本来の更新日時や作成日時とずれたり、採番がかぶったりと
思わぬバグを生み出したり、不具合が起きた時の調査の弊害になります。

障害をさけるためにも、データベース側で生成すべき値はデータベース側で生成したいですね。

では。