Entity Framework Feature CTP 4 で MySQL 使用 - モデル間に一対多の関連

ADO.NET Entity Framework Feature Community Technology Preview 4 (以下 EF CTP 4)を使って、MySQL に接続するコンソールアプリのサンプルを作成してみました。

EF CTP 4 は ADO.NET Entity Framework(.NET Framework 4 に含まれている)に対する機能拡張で "コード・ファースト開発" を実施するためのものです。

コード・ファースト開発は、コーディングを中心に CoC(Convention over Configuration)に基づいた開発を可能にします。(Grails の GORM のようにプレーンなモデルクラスから、DB テーブル定義の自動作成等が可能)

使用した環境は以下の通り。

サンプルのソースは http://github.com/fits/try_samples/tree/master/blog/20101117/

なお、今回は以下のページを参考にしました。

事前準備

まず、以下をダウンロードしてインストールしておきます。

モデル・コンテキストクラスの作成

モデルクラスとコンテキストクラスを作成します。
DB への操作を実施するためのコンテキストクラスは DbContext のサブクラスである必要がありますが、モデルクラスは POCO(plain old clr object)で書けます。

注目する点は以下。

  • コンテキストクラスに DB への永続化を行うモデルクラスを DbSet 型のプロパティとして定義
  • モデル間の関連は相手先モデル型のプロパティを定義

以下のサンプルでは Publisher と Book が一対多の関連を持つように定義しています。(プライマリキー用のフィールドは必須なので注意)

MySQLSample.cs(モデル・コンテキストクラス定義)
・・・
using System.Data.Entity;
・・・
namespace Fits.Sample
{
    ・・・
    //モデルクラス
    public class Publisher
    {
        public int PublisherId { get; set; }
        public string Name { get; set; }
        public string Address { get; set; }
        //複数 Book への関連を定義
        public ICollection<Book> Books { get; set; }
    }

    //モデルクラス
    public class Book
    {
        public int BookId { get; set; }
        public string Title { get; set; }
        //Publisher への関連を定義
        //(PublisherId というプロパティは不要)
        public Publisher Publisher { get; set; }
    }

    //コンテキストクラス
    public class BookManager : DbContext
    {
        public DbSet<Publisher> Publishers { get; set; }
        public DbSet<Book> Books { get; set; }
    }
}

データ追加・検索処理の作成

コンテキストクラスを使ってデータの追加や検索を実施します。

データの追加はコンテキストクラスのプロパティにモデルオブジェクトを Add し、SaveChanges するだけです。

検索は DbSet が用意しているメソッドを使う事になりますが、今回は LINQ 形式で実装してみました。

なお、対多の関連にあるプロパティの内容も予めロードしておくには、LINQ で Include(プロパティ名) を指定します。(Include が無いと対多のプロパティは null になる)


また、Database.SetInitializer() にて RecreateDatabaseIfModelChanges を設定しておけば、モデルの変更時に自動的にテーブルを再作成してくれるようになります。(今回のサンプルでは最初のテーブル定義作成時にも設定が必要でした。ただし、最初に作るだけなら CreateDatabaseOnlyIfNotExists でいいかも)

MySQLSample.cs(データ操作)
・・・
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
・・・
namespace Fits.Sample
{
    class MySQLSample
    {
        static void Main(string[] args)
        {
            //モデル変更時にテーブルを再作成するための設定
            Database.SetInitializer<BookManager>(new RecreateDatabaseIfModelChanges<BookManager>());
            AddData();
            SelectData();
        }

        //データ追加
        private static void AddData()
        {
            using (var manager = new BookManager())
            {
                var p1 = new Publisher
                {
                    Name = "テスト1",
                    Address = "神奈川県・・・"
                };
                manager.Publishers.Add(p1);
                manager.Publishers.Add(new Publisher
                {
                    Name = "test2",
                    Address = "東京都・・・"
                });

                manager.Books.Add(new Book
                {
                    Title = "Entity Framework CTP4",
                    Publisher = p1
                });
                manager.Books.Add(new Book
                {
                    Title = "MySQL",
                    Publisher = p1
                });
                //DBへの保存
                manager.SaveChanges();
            }
        }

        //データ検索
        private static void SelectData()
        {
            using (var manager = new BookManager())
            {
                //Include で Books の内容をロードするように指定
                //Include の指定が無いと Books プロパティの値が null になる
                var res = from p in manager.Publishers.Include("Books")
                          where p.Books.Count > 0
                          select p;

                res.ToList().ForEach(p =>
                {
                    Console.WriteLine("publisher: {0}", p.Name);
                    p.Books.ToList().ForEach(b => Console.WriteLine("book: {0}", b.Title));
                    Console.WriteLine();
                });
            }
        }
    }
    ・・・
}

DB接続文字列の設定

アプリケーション構成ファイルに MySQL への接続文字列を設定します。(Visual C# を使うのなら、App.config を追加してその中に設定)

name 属性にコンテキストクラスの名称を使用する点に注意。

MySQLSample.exe.config(接続先の MySQLlocalhost
<configuration>
  <connectionStrings>
    <add name="BookManager" connectionString="database=booktest;uid=root;charset=utf8" providerName="MySql.Data.MySqlClient" />
  </connectionStrings>
</configuration>

本来であれば、この接続文字列だけで DB 作成も自動で行ってもらえるような気がするのですが、MySQL に booktest が無いと実行時に ProviderIncompatibleException が発生するため、booktest の DB 定義は事前に作成しておきます。

booktest DB の作成
> mysql -u root
・・・
mysql> create database booktest character set utf8;
・・・

ビルドと実行結果

ビルドは以下のように Microsoft.Data.Entity.CTP.dll を参照に指定して実行。(Visual C# では「参照」に追加すればよい)

csc.exe を使ったビルド
> csc /r:"<EF CTP4 のインストール先ディレクトリ>\Binaries\Microsoft.Data.Entity.CTP.dll" MySQLSample.cs
実行結果
> MySQLSample.exe
publisher: テスト1
book: MySQL
book: Entity Framework CTP4

実行すると booktest 内に 3つのテーブルが自動的に作成され、データが登録されている事が確認できます。

  • booktest データベース
    • books テーブル
    • publishers テーブル
    • edmmetadata テーブル

ちなみに、上記の books・publishers テーブル定義をエクスポートしてみると、以下のようになりました。
books に PublisherId が定義されている点に注目。

books・publishers のテーブル定義
CREATE TABLE `books` (
  `BookId` int(11) NOT NULL AUTO_INCREMENT,
  `PublisherId` int(11) DEFAULT NULL,
  `Title` varchar(4000) CHARACTER SET utf8 DEFAULT NULL,
  PRIMARY KEY (`BookId`),
  KEY `PublisherId` (`PublisherId`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;

CREATE TABLE `publishers` (
  `Address` varchar(4000) CHARACTER SET utf8 DEFAULT NULL,
  `Name` varchar(4000) CHARACTER SET utf8 DEFAULT NULL,
  `PublisherId` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`PublisherId`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;