Entity Framework Code First を ORMapperとして使う

いいツールがリリースされています。
その名も Entity Framework Power Tools です。まだβだけど、これを使うことで、データベースからPOCOクラスをリバースエンジニアリングしてくれるし、PKの設定や列長の制限、FKの設定までもFluent APIで自動的に記述してくれます。なにより、Microsoft謹製!

つまり、これを使ってリバースエンジニアリングした結果を見れば、Web上にはあまり解説がなくて苦労する Fluent API の書き方も理解できちゃうわけです。これは便利!

惜しいのは、テーブルやビューなどを指定してリバースエンジニアリングができないことです。データベース単位でまとめてドーン!。まだ β ということで、正式リリースの時にはもっと便利になっているといいですね。

インストールはVisual Studio 2010 のツール → 拡張機能マネージャーから簡単にインストールできます。

さて、このEF Power Toolsは、βのくせになかなかに便利なんだけども、ちょっとだけカスタマイズしておいた方がより幸せになれます。
というわけでその方法です。

プロジェクト上で右クリックすると Entity Frameworkなんてメニューが追加されているはずです。その中の Customize Reverse Engineer Templates をクリック。

20120414_01

なんかセキュリティがどうのこうの言うけど、無視してOKクリック。・・・嫌ならキャンセルクリックでもいいです。結果は同じです。

20120414_02

あれ、同じダイアログがまたあがってきた?ってなるけど、あと2回OK(またはキャンセル)クリックしてればダイアログはあがらなくなります。
これは、テンプレートファイルが3つ作成されるから、3回ダイアログがみたいです。

で、プロジェクトをよく見ると、テンプレートファイルであるttファイルが3つ生成されているはずです。

20120414_03

そしたらまずはEntity.ttを開きます。そして、13行目にある、public class を、public partial class に変えます。

20120414_04

こんどは、Mapping.ttを開きます。同じように、33行目の public class を、public partial class に変え、さらに35行目に

partial void AddReferences();

を追加します。39行目に

this.AddReferences();

を追加します。次のようになればOKです。

20120414_05

これで準備はOKです。これをしておくと何が素敵かっていうと、リーバスエンジニアリングで自動的に生成された結果に何かを手を加えてしまうと、リバースエンジニアリングによる再生成をやりにくくなる・・・だって、再生成しちゃうと、せっかくカスタマイズした部分が消えちゃうから、一度どこかに退避しておいて、再生成後に手でマージして・・・なんて作業、いちいちやってられるか!・・・ってことが防げるってことです。

あれ、でも Entity Framework Power Tools を使えば自動的に全部設定をFluent APIで作ってくれるわけだから、カスタマイズなんかしなくてもいいんじゃないの?って思うかもしれません。
でもテーブル間のリレーションって、設計時にはもちろん考えるけど、物理的に外部参照制約は貼らないって場合多くないですか?だって、開発時には邪魔にしかならないし、運用の妨げになることも多いからって考えの人が結構いるのが現実。個人的にはFKは貼れ!って思ってますが。
んで、Entity Framework Power Tools のリバースエンジニアリングは、FK貼ってなかったら当たり前だけどクラス間の参照設定は作ってくれません。
だから自分で作るしかないのです。これがカスタマイズ部分、ってわけ。

ツール+人間で1つのクラスを作るっていうパターンのために partial クラスは作られたようなもんだから、すごく真っ当な使い方だと思うけどどうです?

さて、じゃあ次はそのカスタマイズいってみましょう。リバースエンジニアリングして次の2つのPOCOが生成された、とします。

using System;
using System.Collections.Generic;

namespace ConsoleAppEF.Models
{
    public partial class Customer {
        public int CustomerId { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
        public string Phone { get; set; }
        public string PasswordHash { get; set; }
    }
}
using System;
using System.Collections.Generic;

namespace ConsoleAppEF.Models
{
    public partial class Reservation {
        public int ReservationId { get; set; }
        public int CustomerId { get; set; }
        public System.DateTime ReservationStartDateTime { get; set; }
        public System.DateTime ReservationEndDateTime { get; set; }
    }
}

Customer(顧客)クラスは、Reservation(予約)クラスを複数持つ、つまり1:多の関係にあります。
ただ、データベース上ではFKを作っていなかったので、クラス間に参照はない。これを今から作っていきます。

まずはカスタマイズした設定を保持するためにModelsフォルダ内にフォルダを作る。カスタマイズ内容はクラス間の参照しかないはずなので、フォルダ名はReferencesにする。ここへカスタマイズクラスを作成する。

まずはCustomerクラスです。こいつはReservationクラスを複数持つので、ICollection<T>でプロパティを作る必要があります。
まずはCustomerクラスのファイルを ReferencesフォルダへそのままCopyしちまいましょう。名前空間がまったく同じpartialクラスを作るわけなので、こうやってあらかじめコピーしておけばミスがないです。

20120414_06

では、プロパティを書きます。次のようになるはず。virtual を忘れないように。

using System;
using System.Collections.Generic;

namespace ConsoleAppEF.Models
{
    public partial class Customer {
        public virtual ICollection<Reservation> Reservations { get; set; }
    }
}

今度はReservationクラス。ReservationクラスからはCustomerクラスを1つだけ参照する(保持する)わけなので、プロパティは次のようになります。
あぁ、あらかじめReferencesフォルダへコピーしてから作業しましょう。

using System;
using System.Collections.Generic;

namespace ConsoleAppEF.Models
{
    public partial class Reservation {
        public virtual Customer Customer { get; set; }
    }
}

さて、最後にオブジェクト間の参照の設定をしてやらなくてはならない。実は今回はこの設定をしなくても、規約に則ったプロパティ名なのでオブジェクト間の参照設定は必要ありません。(具体的に言うと、ReservationクラスのCustomerIdが規約に則った命名になっている)

でも規約に則っていない列名の方が世の中多いよねーということで、その場合はFluent APIでさらっと書けばよいでしょう。
今度はMappingフォルダ内にReferencesフォルダを作ることにしよう。そして、1:多の関連を書くわけだけども、この場合、「多」のクラスから見た場合の関連を書くっていうのがFluent API のお作法のようです。つまり今回の場合でいうと、Reservationクラスの設定ファイルである、ReservationMapクラスに書くわけだ。

では早速、Mapping\ReferencesフォルダにReservationMapクラスをコピー。次のように書きます。

using System.ComponentModel.DataAnnotations;
using System.Data.Entity.ModelConfiguration;

namespace ConsoleAppEF.Models.Mapping
{
    public partial class ReservationMap : EntityTypeConfiguration<Reservation>
    {
         partial void AddReferences()
        {
            this.HasRequired(t => t.Customer)
                .WithMany(f => f.Reservations)
                .HasForeignKey(k => k.CustomerId);
        }
    }
}

設定の記述は、partial void AddReferencesメソッド内に書きます。これは部分メソッドで、コピー元のReservationMapのコンストラクタの先頭で呼ぶようにさっきカスタマイズしたところです。

部部メソッドは、中身を実装しなくても問題ないから、参照設定が必要あるクラスだけ書けばいい、という仕掛けです。

ではCustomerオブジェクトとReservationオブジェクトの参照を維持したままデータを取得してみましょう。オブジェクト参照を維持してSelectする方法はIncludeメソッドを使いますが、使い方は2種類あります。一つは、プロパティ名を文字列で指定する方法。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using ConsoleAppEF.Models;

namespace ConsoleAppEF
{
    class Program {
        static void Main(string[] args)
        {
            using (var context = new MyDbContext())
            {
                var customerList = (from a in context.Customers.AsNoTracking().Include("Reservations")
                                   select a).ToList();

                Console.WriteLine("count : {0}", customerList.Count);

                Console.ReadLine();
            }
        }
    }
}

でも、これだとプロパティ名を文字列で指定って、非常に気に入らないですね。
そこで、System.Data.Entityをusing で参照しましょう。ラムダ式で指定が可能になります。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using ConsoleAppEF.Models;
using System.Data.Entity;

namespace ConsoleAppEF
{
    class Program {
        static void Main(string[] args)
        {
            using (var context = new MyDbContext())
            {
                var customerList = (from a in context.Customers.AsNoTracking().Include(m => m.Reservations)
                                   select a).ToList();

                Console.WriteLine("count : {0}", customerList.Count);

                Console.ReadLine();
            }
        }
    }
}

これなら、ばっちり。

というわけで、Entity Framework Code FirstをORMapperとして使用できる環境が本格的に整った!と思いませんか?

カテゴリー: EntityFramework パーマリンク

コメントを残す