Entity Framework CTP 5 Code First ウォークスルー

「Code Firstは気持ちいい」 、私の前のデスクに座っている @haruulala さんが呟いていました。

確かに、コーディング大好きデベロッパーには納得の一言だと思います。今日は、2010年では最後のバージョンとなる CTP5 を実際に動かしてみましょう。ちなみにCTP4からは、一部変更されているところもあります。変更点については以前のポスト(Entity Framework Code First CTP5 リリース)を参考にしてください。 「そもそもCode Firstって何よ?」という方も以前のポスト(ADO.NET Entity Framework Code First を使ってみよう)を参考にしてください。

 

必要な環境

・Visual Studio 2010

・SQL Server 2008/R2 Express 

 

1. EF CTP5をインストール

※以前のバージョンが入っていても削除する必要はありません。

 

2.コンソールアプリ作成

    1. Visual Studio 2010 を起動
    2. ファイル-> 新規作成 -> プロジェクト
    3. プロジェクト名は”CodeFirstSample”としてコンソールアプリケーション作成
    4. “OK” をクリック

 

3.モデル実装

検証用であればProgram.csにそのままつくっちゃいましょう。

ここでは1:Nの関係をもつエンティティを2つ定義しています。

(本当は多:多の方がEFのメリットが分かりやすいかもしれません・・・今回はシンプルさを重視して1:N)

 public class Category{    public string CategoryId { get; set; }    public string Name { get; set; }    public virtual ICollection<Product> Products { get; set; }}
 public class Product{    public int ProductId { get; set; }    public string Name { get; set; }    public string CategoryId { get; set; }    public virtual Category Category { get; set; }} 

  

4.コンテキスト 実装

コンテキストの作成には以下2つのオブジェクト(System.Data.Entity.DbContext、System.Data.Entity.DbSet<TEntity>)が必要になります。そのため、以下二つのコンポーネントを参照追加します。

    • EntityFramework
    • System.Data.Entity

これも検証用であればProgram.csにそのままつくっちゃいましょう。using System.Data.Entityをお忘れなく。

 ・・
 using System.Data.Entity;
 public class ProductContext : DbContext{    public DbSet<Category> Categories { get; set; }    public DbSet<Product> Products { get; set; }}

以上で完成です。コードファーストでは”Create Database”や”Create Table”やデザイナでモデルを作るなどなど、様々な作業を省いて実装を簡潔にします。

 

5.実行 1

Program.csに次のコードを追加して、実行してみましょう。

 class Program{    static void Main(string[] args)    {        using (var db = new ProductContext())        {            // カテゴリの追加             var food = new Category { CategoryId = "FOOD", Name = "Foods" };            db.Categories.Add(food);            int recordsAffected = db.SaveChanges();            Console.WriteLine(                "Saved {0} entities to the database, press any key to exit.",                recordsAffected);            Console.ReadKey();        }    }} 

SQL Server Management Studio を使って、SQLExpressの中身を見てください。

”CodeFirstSample.ProductContext”という名前でDBが作成されています。

また、テーブル(Categories)が作成されています。

この辺りのデフォルト値は規約で決まっていまるわけですが、カスタマイズ可能です。これについても後程ふれます。

 

6.実行2

他の処理も確認してみましょう。

EFやLINQをご存じの方であれば、そのままのスキルで実装可能です。

 class Program{    static void Main(string[] args)    {        using (var db = new ProductContext())        {            // カテゴリの存在チェック            var food = db.Categories.Find("FOOD");            if (food == null)            {                food = new Category { CategoryId = "FOOD", Name = "Foods" };                db.Categories.Add(food);            }            // プロダクトの追加             Console.Write("Please enter a name for a new food: ");            var productName = Console.ReadLine();            var product = new Product { Name = productName, Category = food };            db.Products.Add(product);            int recordsAffected = db.SaveChanges();            Console.WriteLine(                "Saved {0} entities to the database.",                recordsAffected);            // プロダクトを取得            var allFoods = from p in db.Products                            where p.CategoryId == "FOOD"                            orderby p.Name                            select p;            Console.WriteLine("All foods in database:");            foreach (var item in allFoods)            {                Console.WriteLine(" - {0}", item.Name);            }            Console.WriteLine("Press any key to exit.");            Console.ReadKey();        }    }} 

 

補足ですがLINQを利用した場合、従来のEFと同様に遅延ロードや、Includeオプションの指定も可能です。

遅延ロードの例

using (var db = new ProductContext()) { var allcats = from c in db.Categories select c;

     foreach (var c in allcats) { Console.WriteLine(c.Name); foreach (var p in c.Products) { Console.WriteLine(p.Name); } } }

 

Includeオプションの例

using (var db = new ProductContext()) { var allcats = from c in db.Categories.Include("Products") select c;

     foreach (var c in allcats) { Console.WriteLine(c.Name); foreach (var p in c.Products) { Console.WriteLine(p.Name); } } }

 

7.DBの名前を変更

”CodeFirstSample.ProductContext”という名前のDBを”MyProductDatabase”という名前に変更してみましょう。

作成したContextの実装を以下のように修正します。するとbaseに指定した名称にDB名が変更されます。

 public class ProductContext : DbContext{    public ProductContext()        : base("MyProductDatabase")    { }    public DbSet<Category> Categories { get; set; }    public DbSet<Product> Products { get; set; }} 

補足ですが接続文字列を使っても、DB名、インスタンス名、認証方式など指定できます。

例えば設定ファイル(App.config)に下記の設定を追加すると、MSSQLServerにProductsデータベースが生成されます。

<configuration> <connectionStrings> <add name="MyProductDatabase" connectionString="Data Source=.;Initial Catalog=Products; Integrated Security=True;MultipleActiveResultSets=True" providerName="System.Data.SqlClient"/> </connectionStrings> </configuration>

 

8.スキーマ変更

スキーマの変更も可能です。

例えば、エンティティの追加です。

以下のSupplierをProgram.csへ追加しましょう。

 public class Supplier{    public string SupplierCode { get; set; }    public string Name { get; set; }}

Contextも次のようにSupplierに関するメソッドを追加します。

 public class ProductContext : DbContext{    public ProductContext()        : base("MyProductDatabase")    { }    public DbSet<Category> Categories { get; set; }    public DbSet<Product> Products { get; set; }    public DbSet<Supplier> Suppliers { get; set; }}

この状態で実行すると、Supplierがキーを持っていないというエラーが発生してしまいます。

「あれっ??なんだよ、勝手にSupplierCodeをキーにしてくれよ~。」と愚痴りたくなるかもしれませんが、SupplierCodeがプライマリーキーの規約にマッチしないのが原因です。詳しくはこちらを参照してください。

従って、明示的にキーを指定する必要があります。

参照の追加で”System.ComponentModel.DataAnnotations”コンポーネントを追加して、以下の名前空間を追加してください。

 using System.ComponentModel.DataAnnotations;

そして、次のようにKey属性を追加します。

 public class Supplier{    [Key]    public string SupplierCode { get; set; }    public string Name { get; set; }}

ちなみにCTP5でサポートしているアノテーションは以下の通り。

  • KeyAttribute
  • StringLengthAttribute
  • MaxLengthAttribute
  • ConcurrencyCheckAttribute
  • RequiredAttribute
  • TimestampAttribute
  • ComplexTypeAttribute
  • ColumnAttribute
  • TableAttribute
  • InversePropertyAttribute
  • ForeignKeyAttribute
  • DatabaseGeneratedAttribute
  • NotMappedAttribute

 

Program.csに以下の名前空間と、処理を追加するとスキーマの再生成が可能です。

※といっても以下を実行するとデータが消えちゃうのでお気をつけて

 using System.Data.Entity.Database;
 DbDatabase.SetInitializer<ProductContext>(    new DropCreateDatabaseIfModelChanges<ProductContext>());

 

9.バリデーション

最後にアノテーションを利用したデータ検証を試してみましょう。

例えば、次のようにMinLength、MaxLength指定することで、文字列長の範囲を指定できます。

 public class Supplier{    [Key]    public string SupplierCode { get; set; }    [MinLength(5)]    [MaxLength(20)]    public string Name { get; set; }}

エラーが発生した時の処理を追加するため、Program.csに次の名前空間を追加します。

 using System.Data.Entity.Validation;

Program.csに次の処理を追加して、実行するとエラーが発生します。これはSupplierのNameに値が3文字しか割り当てられておらず、データ検証で失敗してしまったことが原因です。

 class Program{    static void Main(string[] args)    {        DbDatabase.SetInitializer<ProductContext>(            new DropCreateDatabaseIfModelChanges<ProductContext>());        using (var db = new ProductContext())        {            var supplier = new Supplier { Name = "123" };            db.Suppliers.Add(supplier);            try            {                db.SaveChanges();            }            catch (DbEntityValidationException ex)            {                foreach (var failure in ex.EntityValidationErrors)                {                    Console.WriteLine(                        "{0} failed validation",                         failure.Entry.Entity.GetType());                    foreach (var error in failure.ValidationErrors)                    {                        Console.WriteLine(                            "- {0} : {1}",                             error.PropertyName,                             error.ErrorMessage);                    }                }            }            Console.WriteLine("Press any key to exit.");            Console.ReadKey();        }    }}

ということで、いろいろとCTP5 Code Firstの機能を試してみました。

気持ちよく、動かせたでしょうか?