EntityFramework4.1とSQL Server Compact 4.0でCodeFirstことはじめ
id:okazukiさんの、id:okazuki:20110320:1300628620 この日記を見て、目から鱗だったので、早速やってみようと思い色々いじった結果をメモ。
基本的に今まで、組み込みのDB歯科使って無くて、対象がSQLiteをSystem.Data.SQLiteで動かしていたので、ちょこっとEntityFrameworkや、SQL ce 3.5はいじったことあったけど、そもそも、EntityFramework経由でDBが生成した例えば、インクリメントするIdみたいなモノを取得出来ない結構致命的な制約があったので、あんまりいじってなかった。
で、色々見ていたら、EntityFrameworkが4.1になり、SQL ceも4.0になって、良い感じで環境が整った上に、組み込み目的なら、ほぼそれこそDDLすら書く必要が無くなったらしいので、試してみたところ、何かもう魔法使ってる気分。
で、若干はまりそうな部分があったので、その辺を備忘録的に。
用意すべきモノ
以下の2つをインストールする必要がある。
- VisualStudio 2010 SP1*1
- Microsoft SQL Server Compact 4.0 Tools
- EntityFramework4.1
SP1と、Toolsは、Web Platform Installer : The Official Microsoft IIS Siteから、DL可能。
Entity FrameworkはNuGetを使うか、Download ADO.NET Entity Framework 4.1 from Official Microsoft Download Centerここから、インストーラパッケージをDLするコトが出来る*2。
プロジェクトへの仕込み
プロジェクトへ参照で、詰まりそうな部分は以下の通り。
- EntityFramework4.1*3
- System.Data.SqlServerCe*4
- System.ComponentModel.DataAnnotations
- System.Data.Entity
あとは、必要に応じて参照を加えていけばおっけー
で、テーブルの定義
なワケだけど、ここが凄くて、一切SQLはおろか、デザイナすらいじる必要が無いw
今回作ったテーブルを、DDLで書くと大体こんな感じになる
CREATE TABLE SampleTable ( Id int IDENTITY (1,1) PRIMARY KEY, Value nvarchar(4000) NOT NULL );
で、キモは、これを一切書かなくても、テーブルが作れてしまうと言うコト。
とりあえず、このモデルをクラスにして以下のように書く
using System; using System.ComponentModel.DataAnnotations; using System.Data.Entity; namespace EntityFrameworkSample { [Table("SampleTable")] public class SampleData { public SampleData() { //これは必須。 //Queryの戻りを貰ってくるとき、これが無いと例外吐いてこける。 } public SampleData(string value) { if (value == null) { throw new ArgumentNullException("value"); } Value = value; } [Key] [DatabaseGenerated(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.Identity)] public int Id { get; set; } [Required] public string Value { get; private set; } public void SetData(string value) { if (value == null) { throw new ArgumentNullException("value"); } } public override string ToString() { return "Id:" + Id.ToString() + "\t" + "Value:" + Value; } } }
classに対する属性[Table]は今回、この”SampleData”を、"SampleTable"にマップしたかったんで、こんな風に書いたけど、ClassNameと一致してて問題なければ別段必要なかったりする。
次の、[Key]は、Idプロパティが一意であるコトを表現するための属性。
で、お待ちかねのサーバサイドでこのIdを生成させるための属性が、[DatabaseGenerated(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.Identity)]。これを付けることで、Idの生成をサーバサイドで行うことが可能になる。
次に、DbContextを継承した、SampleContextClassを作り、今作った、SampleDataをIDbSetとして公開する。このコードは以下
using System; using System.ComponentModel.DataAnnotations; using System.Data.Entity; using System.Linq; namespace EntityFrameworkSample { public class SampleContext : DbContext { public SampleContext(string conStr) : base(conStr) { } public IDbSet<SampleData> Data { get; set; } } }
id:okazukiさんのサンプルのように、App.Configで、ConnectionStringを指定してる場合は、Ctorは不要だけど、今回、コード内で対象となるファイルへのパスを指定したかったので、そいつを受け入れるためのctorを作成してある。
で、ここまで仕上がったら、実際に動かしてみる。
コンソールアプリなので、以下みたいなCRUDと表示をさせてみた。
using System; using System.ComponentModel.DataAnnotations; using System.Data.Entity; using System.Linq; namespace EntityFrameworkSample { class Program { static void Main(string[] args) { Database.DefaultConnectionFactory = new System.Data.Entity.Infrastructure.SqlCeConnectionFactory("System.Data.SqlServerCe.4.0"); Database.SetInitializer(new DropCreateDatabaseAlways<SampleContext>()); using (var context = new SampleContext("G:\\hoge.sdf")) { //追加 Console.WriteLine(context.Data.Add(new SampleData("hello"))); Console.WriteLine(context.Data.Add(new SampleData("world"))); context.SaveChanges(); Console.WriteLine("Data saved"); //結果を表示 foreach (var elem in context.Data) { Console.WriteLine(elem); } //Update context.Data.First().SetData("こんにちは"); context.SaveChanges(); Console.WriteLine("Data saved."); //アップデート結果を表示 foreach (var elem in context.Data) { Console.WriteLine(elem); } //削除 context.Data.Remove(context.Data.First()); context.SaveChanges(); Console.WriteLine("Data saved."); //削除結果を表示 foreach (var elem in context.Data) { Console.WriteLine(elem); } } Console.WriteLine("Press enter to exit."); Console.ReadLine(); } } }
初っぱなで、DefaultConnectionFactorを設定してるのは、デフォルトだと、SQL serverになってしまうため。今回扱っているのは、Ceの方なので、デフォルトをCeに再設定する必要があるため、こんな風に指定する必要がある。
また、次のSetInitializerは、起動させる毎に、Dbを再作成してくれよと、指定してる。
また、SaveChagesメソッドを呼び出さない限り、表示は変わらないので、この辺も注意しないとダメかも。。。