かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Entity Framework 4.1 CodeFirstで入れ子になったオブジェクトのプロパティをテーブルにマッピング

長いBlogタイトルですが、タイトルのようなことをやりたくなりました。というのも、最近「エリック・エヴァンスのドメイン駆動設計」を読んでるんですが、ドメインを設計したやつをRDBにマッピングすることをうんぬんと書いてたりするのを見てEntity Frameworkという優秀な子を思い出したからです。


普通にクラス定義してプロパティをカラムにマッピングするのは簡単なんですが「プロパティがクラスとして定義された別の型で、なおかつ、そのクラスがDBのテーブルにマッピングされてないときどういう動きをするのか?」ということを試してみました。

エンテティとDbContextの定義は以下のような感じです。

namespace ConsoleApplication6
{
    using System.Data.Entity;

    /// <summary>
    /// DbContext
    /// </summary>
    public class SampleContext : DbContext
    {
        public IDbSet<Person> People { get; set; }
    }

    /// <summary>
    /// Personテーブルに対応するクラス
    /// </summary>
    public class Person
    {
        public long ID { get; set; }

        /// <summary>
        /// 今回の主役!型は下で定義しているNameクラス
        /// </summary>
        public Name FullName { get; set; }
    }

    /// <summary>
    /// こいつはNameテーブルみたいにマッピングはされない
    /// </summary>
    public class Name
    {
        public string First { get; set; }
        public string Last { get; set; }
    }
}

これに対して、以下のようなMainプログラムを書いて実行してみました。

namespace ConsoleApplication6
{
    using System;
    using System.Data.Entity;
    using System.Linq;

    class Program
    {
        static void Main(string[] args)
        {
            // モデルに変更があったらDBをつくりなおす
            Database.SetInitializer<SampleContext>(
                new DropCreateDatabaseIfModelChanges<SampleContext>());

            // データの登録
            using (var c = new SampleContext())
            {
                c.People.Add(new Person { FullName = new Name { First = "a", Last = "b" } });
                c.SaveChanges();
            }

            // データの一覧表示
            using (var c = new SampleContext())
            {
                foreach (var p in c.People.AsEnumerable())
                {
                    Console.WriteLine("{0} {1} {2}", p.ID, p.FullName.First, p.FullName.Last);
                }
            }
        }
    }
}

実行結果は、割とどうでもいいので省略します。この結果PersonクラスとNameクラスがどういう風にDBに反映されたか見てみました。

Personテーブルの中に「Personクラスのプロパティ名_Nameクラスのプロパティ名」という命名規則でカラムが作成されました。
カラムが沢山あるテーブルでも、簡単に意味のある単位に分割してクラスに格納できるって寸法ですね。因みに、この名前が気に入らないときは、下記のようにColumn属性をつけてカラム名をカスタマイズできます。ここら辺は通常のプロパティをカラム名にマッピングするのと同じですね。

namespace ConsoleApplication6
{
    using System.Data.Entity;
    using System.ComponentModel.DataAnnotations;

    /// <summary>
    /// DbContext
    /// </summary>
    public class SampleContext : DbContext
    {
        public IDbSet<Person> People { get; set; }
    }

    /// <summary>
    /// Personテーブルに対応するクラス
    /// </summary>
    public class Person
    {
        public long ID { get; set; }

        /// <summary>
        /// 今回の主役!型は下で定義しているNameクラス
        /// </summary>
        public Name FullName { get; set; }
    }

    /// <summary>
    /// こいつはNameテーブルみたいにマッピングはされない
    /// </summary>
    public class Name
    {
        // カラム名の指定方法は通常のプロパティのときと同じ
        [Column("FirstName")]
        public string First { get; set; }
        [Column("LastName")]
        public string Last { get; set; }
    }
}

以下に、この状態でプログラムを再実行したときに生成されるDBを示します。ちゃんと属性で指定したカラム名になっているのがわかります。

以上、簡単でしたが普通よりほんのちょっと複雑?なテーブルとクラスのマッピングについて紹介でした。