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つをインストールする必要がある。

  1. VisualStudio 2010 SP1*1
  2. Microsoft SQL Server Compact 4.0 Tools
  3. 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

プロジェクトへの仕込み

プロジェクトへ参照で、詰まりそうな部分は以下の通り。

  1. EntityFramework4.1*3
  2. System.Data.SqlServerCe*4
  3. System.ComponentModel.DataAnnotations
  4. 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メソッドを呼び出さない限り、表示は変わらないので、この辺も注意しないとダメかも。。。

今回のまとめ

データベースは何かと使うコトが多いけど、いかんせん、DDL書いたり、デザイナで作成したり、DML書いたりと何かと煩雑だったのが、DMLはおろかDDLすら書かなくても出来ちまうって点は相当衝撃的だった。
ただ、万能かというと、そーでも無くて、現状以下のような制約がある

  1. Enumは使えない*5
  2. Spatial Dataのサポート*6
  3. ストアドプロシージャ
  4. マイグレーション
  5. カスタマイズされた変換

とはいえ、Ceであれば、そこまで複雑なことはしないし、かなりどころか相当に強力な武器になるのでは無いかな?というのが、触ってみた感じでした。

*1:ここにce4.0が入ってる

*2:NuGetの使い方がいまいちなので、おいらはインストーラーパッケージを使った。そのうちNuGetもちゃんと調べたいなぁと

*3:GACに登録されてるので、参照の追加から、EntityFrameworkを選択

*4:選ぶべきはもちろん4.0の方

*5:探すとわかるけど、こいつに該当する属性は実はあったりする。

*6:こいつに関してはちょっとよく分かんなかった。