Entity Framework 4.1 から従来の(モデルファースト、データベースファースト) に加えて、コードファースト 開発が行えるようになりました。 Entity Framework 4.3 に触れる機会があったので、簡単なサンプルを掲載します。といっても Data Annotations や Fluent API , Database Initializer 等いろいろ使ってみています。

動作確認環境

  • Visual Studio 2010 Professional
  • Entity Framework 4.3
  • SQL Server 2008 R2 Express

Entity Framework 4 Code First は別のページにまとめを用意していますのでそちらも参照してください。

Entity Framework 4 Code First まとめ
http://www.pine4.net/wiki/ef4.Entity-Framework-4-Code-First.ashx

本記事は、NuGet パッケージがインストールされていることが前提です。インストールされていないばあは、 NuGet パッケージをインストールしてください。

1. Entity Framework のインストール

Visual Studio 2010 を起動し、 コンソールプロジェクトを作成します。メニューのプロジェクト→ Manage NuGet Packages をクリックします。

Online タブを選択して、検索テキストに EntityFramework と入力します。 EntityFrameworkが検索結果に表示されるので Intall ボタンをクリックします。

Package Manager Console から Entity Framework をインストールする場合は、下記参照。

メニューから ツール→Library Package Manager → Package Manager Console を選択します。Package Manager Console 上で、下記のようにコマンドを入力すればインストールは完了です。プロジェク毎にインストールする必要があります。

Install-Package EntityFramework

2. ソースプログラムを作成する

モデルクラスを新規作成します。プロジェクトに ForumModel.cs ファイルを追加します。本クラスには複数のクラスを作成します。Categoryクラスと、カテゴリごとのフォーラムを表すThreadクラス、そして各Threadのコメントを表す Comment クラスを定義しています。 後々参考になるように、 [TimeStamp] などの DataAnnotation を使用しています。また、遅延ローディングが使用できるように ナビゲーションプロパティは virtual を設定しています。下記クラスでは モデルの設定を Fluent API で行うために、モデルクラス用の設定クラスのために EntityTypeConfiguration とおまけで ComplexTypeConfiguration(コメント) を使用したサンプルを掲載しています。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity.ModelConfiguration;

namespace CodeFirst01
{
    /// <summary>
    /// カテゴリクラス
    /// </summary>
    public class Category
    {
        public int CategoryId { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public virtual ICollection<Thread> Threads { get; set; }
        [Timestamp]
        public byte[] RowNumber { get; set; }
    }

    /// <summary>
    /// スレッドクラス
    /// </summary>
    public class Thread
    {
        public int ThreadId { get; set; }
        public string Title { get; set; }
        public string Body { get; set; }
        public byte[] TimeStamp { get; set; }
        public virtual Category Category { get; set; }
        public virtual List<Comment> Comments { get; set; }
    }

    /// <summary>
    /// スレッドのコメントクラス
    /// DataAnnotations を使用して制約設定
    /// </summary>
    public class Comment
    {
        [Key]
        public int CommentId { get; set; }
        public int SequenceNumber { get; set; }
        [Column("Message")]
        [MaxLength(255)]
        public string Body { get; set; }
        public CommentUserInfo UserInfo { get; set; }
        [Timestamp]
        public byte[] TimeStamp { get; set; }
        [ForeignKey("Thread")]
        public int ThreadId { get; set; }
        public virtual Thread Thread { get; set; }
    }
    /// <summary>
    /// 複合型:ユーザ情報
    /// </summary>
    [ComplexType]
    public class CommentUserInfo
    {
        public string IpAddress { get; set; }
        public string UserName { get; set; }
        public string HomePage { get; set; }
        public string Email { get; set; }
    }

    /// <summary>
    /// Categoryクラス用の エンティティタイプコンフィギュレーション
    /// クラス
    /// </summary>
    public class CategoryConfiguration : EntityTypeConfiguration<Category>
    {
        public CategoryConfiguration()
        {
            Property(x => x.Name).IsRequired();
            Property(x => x.Description).HasMaxLength(100);
        }
    }
    ///// <summary>
    ///// 複合型である、 CommentUserInfo のコンフィグレーションを行う場合
    ///// </summary>
    //public class CommentUserInfoConfiguration : ComplexTypeConfiguration<CommentUserInfo>
    //{
    //    public CommentUserInfoConfiguration()
    //    {
    //        // Fluent API を使用してエンティティの構成情報を設定
    //    }
    //}
}

次に Context クラスを作成します。 新規に、 ForumContext というクラスを作成します。ForumContextクラスでは、各モデルクラスをプロパティとして保持しています。また、OnModelCreating メソッドをオーバーライドしてモデルクラスとデータベースのコンフィグレーションを行っています。

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

namespace CodeFirst01
{
    public class ForumContext : DbContext
    {
        public DbSet<Category> Categories { get; set; }
        public DbSet<Thread> Threads { get; set; }
        public DbSet<Comment> Comments { get; set; }

        /// <summary>
        /// モデルとエンティティのコンフィグレーションを行う
        /// DataAnnotation ではなく、 FluentAPI を使用する場合に、
        /// OnModelCreating 内で FluentAPI や EntityTypeConfiguration,
        /// ComplexTypeConfiguration を継承したクラスで構成を行う
        /// </summary>
        /// <param name="modelBuilder"></param>
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new CategoryConfiguration());   
            modelBuilder.Entity<Thread>().HasKey(x => x.ThreadId).Property(x => x.Title).HasMaxLength(1024);
            modelBuilder.Entity<Thread>().Property(x => x.TimeStamp).IsRowVersion();
            //// 複合型の構成を行う場合は ComplexType<T>() を使用する
            //modelBuilder.ComplexType<CommentUserInfo>().Property(x => x.IpAddress).HasColumnType("nchar").HasMaxLength(100);

            // EntityFramework Code First 4.1,4.2 で EdmMetadataテーブルを作成し、モデルの変更をを検出しないようにする
            // (開発者がモデルとDBの対応に責任を持つ)には、IncludeMetadataConvention が必要だったが
            //// EntityFramework 4.3以降では IncludemetadataConventionはdepreated (廃止)となった。
            // EdmMetadata テーブルを作成しなくなったため。
            // http://blogs.msdn.com/b/adonet/archive/2012/02/09/ef-4-3-released.aspxの
            // Removal of EdmMetadata table あたりを参照
            //modelBuilder.Conventions.Remove<System.Data.Entity.Infrastructure.IncludeMetadataConvention>();
            
        }
    }
}

モデルクラスとコンテキストクラスが作成できました。あとは、既定で作成される Program.cs を次のように編集して、プログラムが動作することを確認します。サンプルでは モデル作成時にデータベースの初期化の振る舞いを決定する Database Initializer を既定の(CreateDatabaseIfNotExists から DropCreateDatabaseAlways)に変更しています。

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

namespace CodeFirst01
{
    class Program
    {
        static void Main(string[] args)
        {
            // モデルが変更されていたら データベースを作成し直すように Initializer を設定
            //System.Data.Entity.Database.SetInitializer(new DropCreateDatabaseIfModelChanges<ForumContext>());
            //Database.SetInitializer(new CreateDatabaseIfNotExists<ForumContext>()); // 既定.DBがない場合にDBを作成する
            Database.SetInitializer(new DropCreateDatabaseAlways<ForumContext>()); // 初めてForumContext が作成されるときに必ずDBを作成する

            using (ForumContext context = new ForumContext())
            {
                Category category = new Category
                {
                    Name = "ニュース",
                    Description = "ニュース的なこと"
                };
                context.Categories.Add(category);
                context.SaveChanges();
            }
            using (ForumContext context = new ForumContext())
            {
                Category category = context.Categories.FirstOrDefault();

                Thread thread = new Thread
                {
                    Title = "消費税",
                    Body = "いろいろ世知辛いですね。",
                    Category = category,
                    Comments = new List<Comment>()
                };

                Comment comment1 = new Comment
                {
                    SequenceNumber = 0,
                    Body = "コメント1番目",
                    UserInfo = new CommentUserInfo
                    {
                        UserName = "通りすがり1"
                    },
                };
                Comment comment2 = new Comment
                {
                    SequenceNumber = 1,
                    Body = "コメント2番目",
                    UserInfo = new CommentUserInfo
                    {
                        UserName = "通りすがり2"
                    },
                };
                context.Threads.Add(thread);
                thread.Comments.Add(comment1);
                thread.Comments.Add(comment2);
                context.SaveChanges();
            }
            using (ForumContext context = new ForumContext())
            {
                var comments = context.Comments;
                //var comments = context.Comments.Include(x => x.Thread); // Eager Loading を使用する
                foreach (var  c in comments)
                {
                    // 遅延読み込み(Lazy Loading) を使用する場合 MARS(multipleactiveresultsets=true)
                    // を接続文字列に設定する必要があります。複数のアクティブな結果セット(MARS)を
                    // 使用しない場合Include を使用してEager Loading を行います。
                    Console.WriteLine("親スレッド情報:{0}", c.Thread.Title);
                }
                Console.ReadLine();
            }
        }
    }
}

ここまでで準備が完了です。デバッグ実行して動作することを確認して下さい。

SQL Server Express をインストールしていない場合は、 環境に合わせて次のように構成ファイルに接続文字列を設定します。接続文字列名は コンテキストクラスと同名(サンプルでは ForumContext)にしてください。そうでない場合は、接続文字列名を引数にとるコンストラクタを ForumContextに作成する必要があります。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <connectionStrings>
    <add name="ForumContext" connectionString="Server=(local);Initial Catalog=CodeFirst01.ForumContext;Integrated Security=true;multipleactiveresultsets=true" providerName="System.Data.SqlClient"/>
  </connectionStrings>
</configuration>

実行すると、動作の確認ができます。起動時に Database とテーブル、キー設定などが自動で行われます。

3.実行結果

プログラムが正常終了すると、下図のように データベースとテーブル、キー設定などが自動生成されることが確認できます。

SQL Server Management Studio でテーブルのデータを確認すると レコードが作成されていることを確認できます。

プログラムを再実行して 下図のように" XXは現在使用中なので削除できません。" というエラーメッセージが表示される場合は、SQL Server Management Studio でデータベースにクエリを発行するなどしてセッションが残っている可能性があります。その場合は、利用状況モニタを Management Studio から起動して残っているセッションを削除してください。

4.まとめ

説明は以上です。といってもあんまり説明は記載していませんが。Code First に関しては Wiki の Entity Framework 4 Code First まとめに参考になりそうな情報やリンクを掲載しているのでそちらもご参照ください。