Entity FrameworkとSQLiteとAUTOINCREMENT

本当はCodeFirstしたかったんだけど難しそうなので後回し。
Entity Data Modelデザイナを使えばおおかた簡単にできます。

AUTOINCREMENTキーワード

主キーには連番を使うことが多いですが、SQLiteにもそれを助ける機能が備わっています。
SQLiteにはAUTOINCREMENTというキーワードがありまして、テーブル作成するとき列にこれを指定しておくと、値を指定せずにレコード挿入することで連番を振ってくれます。仕組みとしてはsqlite_sequenceというテーブルに最後に生成した値が記録されていて、それに+1した値を振っているわけです。
またINTEGER PRIMARY KEYな列は先のキーワードを指定しなくても連番が生成されます。こちらはテーブルに存在する値の最大値+1が使われます。
これまではこの二つを気分で使い分けていたのですが、EntityFrameworkと組み合わせるとなるとそうもいかなかった、というお話。

StoreGeneratedPattern

挿入時に自動生成される列を扱うポイントは二つあります。

  • 既定値をINSERTしない

既定値のつもりでも値を指定したら自動生成はされません。

  • 自動生成された値が必要ならばINSERT後にSELECTする

INSERT文がレコード返してくれたら便利なのにね。
これをEntityFrameworkに教える方法としてStoreGeneratedPattern列挙体が用意されています。
http://msdn.microsoft.com/ja-jp/library/system.data.metadata.edm.storegeneratedpattern.aspx
EDMデザイナにも表示されます。

AUTOINCREMENTを付けていればこれをIdentityに設定するだけで上手いことやってくれます。

StoreGeneratedPatternとはなんだったのか

ところがAUTOINCREMENTがない場合はいくらStoreGeneratedPattern.Identityを指定しても

  • 既定値をINSERTしない
  • 自動生成された値が必要ならばINSERT後にSELECTする

といった動作をしてくれません。
edmxファイルを比べるとたった一行の違いでした。

<!--AUTOINCREMENTあり-->
<Property Name="Id" Type="integer" Nullable="false" StoreGeneratedPattern="Identity" />
<!--AUTOINCREMENTなし-->
<Property Name="Id" Type="integer" Nullable="false" />

AUTOINCREMENTがない時はデザイナで設定したStoreGeneratedPatternがSSDLに反映されていないという・・・
CSDLにもそれっぽい記述があって、こちらはAUTOINCREMENTなしでも反映されるのですが、これだけでは動作しないようです。

<Property Name="Id" Type="Int64" Nullable="false" annotation:StoreGeneratedPattern="Identity" />

というわけでedmxを手動で直せばAUTOINCREMENTなしでも自動生成が使えましたとさ。めでたしめでたし。