WEB+DB Vol.66の「いまどきの.NET開発 第4回」に記載のあったEntityFramework4.1 コードファースト を少し試してみました。ちょうど1年ほど前に簡単なプログラムを作ろうと思ったときにもEntityFrameworkを利用しようか迷ったのですが、いまいち成熟していない感があったので利用しませんでした。記事を読んだりその他諸々ASP.NET MVC3を調べるうちにそろそろキャッチアップした方がよい技術になったように感じたので試してみました。
コードファーストの詳細については、Entity Framework 4.1入門がよくまとまっています。私も いまどきの.NET開発 第4回 とこちらの記事を参考にしました。また、洋書ですが、Programming Entity Framework: Code First も参考になります。
今回は惑星連邦のエンタープライズ号と乗組員、階級、職務を作成してみました。
まずは乗組員からです。
Crewクラス
[sourcecode language="csharp"] using System; using System.ComponentModel.DataAnnotations;
namespace EntityFrameworkSamples { /// <summary> /// 乗組員 /// </summary> public class Crew { public int Id { get; set; }
[Required]
public string Name { get; set; }
public int? RankId { get; set; }
public int JobId { get; set; }
// 参照設定
public virtual Rank Rank { get; set; }
public virtual Job Job { get; set; }
}
} [/sourcecode]
とても単純なPOCOです。IDは主キーになります。NameにRequired属性がついているのは、String型はデータベースにマップされるとNull可となるためNotNull制約を追加するためです。
参照設定としては、この後に出てくるRankとJobを設定しています。
Rankクラス
[sourcecode language="csharp"] using System.Collections.Generic; using System.ComponentModel.DataAnnotations;
namespace EntityFrameworkSamples { /// <summary> /// 階級 /// </summary> public class Rank { public int Id { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection Crews { get; set; }
}
} [/sourcecode]
Jobクラス
[sourcecode language="csharp"] using System.Collections.Generic; using System.ComponentModel.DataAnnotations;
namespace EntityFrameworkSamples { /// <summary> /// 職務 /// </summary> public class Job { public int Id { get; set; }
[Required]
public string Name { get; set; }
// 関連
public ICollection Crews { get; set; }
}
} [/sourcecode]
次にEnterpriseクラスを定義します。Enterpriseクラスはデータベースに対応します。
[sourcecode language="csharp"] using System.Data.Entity;
namespace EntityFrameworkSamples { /// <summary> /// エンタープライズ /// </summary> public class EnterpriseContext:DbContext { public DbSet Crews { get; set; } public DbSet Ranks { get; set; } public DbSet Jobs{ get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// 制約を設定する
modelBuilder.Entity()
.HasMany(rank => rank.Crews)
// 階級は必須ではない
.WithOptional(crew => crew.Rank)
// cascade on delete は無効とする
.WillCascadeOnDelete(false);
modelBuilder.Entity()
.HasMany(job => job.Crews)
// 職務は必須
.WithRequired(crew => crew.Job)
.WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
}
}
} [/sourcecode]
EnterpriseクラスではOnModelCreatingメソッドをオーバーライドしてFluentAPIを利用することによりデータ削除時の連鎖削除機能をオフにしています。
(この機能が一番わからなくて洋書を購入したのですが、レコードの削除時に関連するレコードを削除する機能をオフにしたかったのです。というのも、SQL Serverには一つのテーブルに複数のテーブルから参照される場合、連鎖参照生成合成制約によりテーブル作成時にエラーとなるのです。これを避けるためには、レコード削除時に関連するレコードを削除する制約をオフにしたかったのです。属性では指定できないらしいので…。)
最後にProgram.csです。
[sourcecode language="csharp"] using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq;
namespace EntityFrameworkSamples { class Program { static void Main(string[] args) {
var jobList = new List
{
new Job() {Name = "艦長", Id = 1},
new Job() {Name = "副長", Id = 2},
new Job() {Name = "第二副長", Id = 3},
new Job() {Name = "美容師", Id = 4},
};
var rankList = new List()
{
new Rank() {Id = 1, Name = "大佐"},
new Rank() {Id = 2, Name = "中佐"},
new Rank() {Id = 3, Name = "少佐"},
};
var crewList = new List
{
new Crew() {Name = "ジャンリュック・ピカード", RankId = 1, JobId = 1},
new Crew() {Name = "ウィリアム.T.ライカー", RankId = 2, JobId = 2},
new Crew() {Name = "データ", RankId = 3,JobId = 3},
new Crew() {Name = "モット", JobId = 4},
};
using (var context = new EnterpriseContext())
{
// モデルに変更があった場合はデータベースを再作成する
// Database.SetInitializer(new DropCreateDatabaseIfModelChanges());
// 常にデータベースを再作成する
Database.SetInitializer(new DropCreateDatabaseAlways());
// データベースが存在しない場合は再作成する
// Database.SetInitializer(new CreateDatabaseIfNotExists());
jobList.ForEach(j => context.Jobs.Add(j));
rankList.ForEach(r => context.Ranks.Add(r));
crewList.ForEach(c => context.Crews.Add(c));
context.SaveChanges();
// 職務でソート
var crews = context.Crews.OrderBy(crew => crew.JobId);
// このように指定するとRankIdを持っていないモットが最上位となる
//var crews = context.Crews.OrderBy(crew => crew.RankId);
foreach (var crew in crews)
{
Console.WriteLine("{0}[ID:{1}]は階級が{2}、役職が{3}です。"
, crew.Name
, crew.Id
, (crew.Rank == null? "なし" : crew.Rank.Name)
, crew.Job.Name);
}
}
}
}
} [/sourcecode]
ピカード艦長はじめクルーを登録しましたが実行結果は次の通りです。
[sourcecode language="language"] ジャンリュック・ピカード[ID:2]は階級が大佐、役職が艦長です。 ウィリアム.T.ライカー[ID:3]は階級が中佐、役職が副長です。 データ[ID:4]は階級が少佐、役職が第二副長です。 モット[ID:1]は階級がなし、役職が美容師です。 [/sourcecode]
これで、全く何もない状態からデータベースが作成され、データベースには4つのテーブルが作成されることになります。詳しくは理解していませんが、EdmMEtadataは更新管理用のテーブルで処理の実行時にモデルと比較することで変更が存在するか確認しています。このテーブルは作成しないようにすることもできるようですが、開発フェーズでは必要でしょう。
これでざっくりとどのような感じなのか理解できました。ただ、残念なことにEntityFrameworkが発行するSQLの内容を確認する方法がわかりませんでした。ManagementStudioのプロファイラを使用すればよいそうですが、Expressでは利用できないようです。また、私が利用しているVisualStudioはProfessionalエディションなのでIntellicenseプロファイラ?というものも利用できません。RailsではDevelopementモードではSQLがログに出力されただけに、SQLを確認できないことは残念です。
また、POCOの参照が論理的なものではなくテーブル間の物理制約となってしまうことも気になります。制約をつけたり外したりすることができるのであればよいのですが、制約がつく以外の方法がないとすれば残念です。
とはいうものの、コードファーストという考え方はとてもすばらしい!テーブル定義したものをそのままエンティティクラスとして利用できるわけで、無駄が一つ減るわけです。EntityFrameworkとASP.NET MVC3を利用することでかなりRailsに近い開発ができるような気がします。ちょっと興味が出てきましたw
ソリューションなど一式はこちらからダウンロードできます。 http://dl.dropbox.com/u/390219/Software/EntityFrameworkCodeFirstSample_20120121.zip
Kindleだとお安いですw