4. Fluent Mapping(1)
ClassMap
ClassMap<T>はマッピングクラスの基底クラスです。
エンテェティのプロパティはコンストラクタの中でマッピングします。
マッピングクラス名は
<エンテェティ名>+Map
が一般的です。
public class PersonMap : ClassMap<Person> { public PersonMap() { } }
ClassMap<T>内のすべてのマッピングはラムダ式を使います。ラムダ式は
x => x.property
の形式で、左辺は引数、右辺が処理内容で最後の文が戻り値となるため、上式はxを渡すと、x.propertyを返すという意味になります。型は、自動推定されるので記述する必要はありません。
Id
エンテェティのマッピングには識別子(Id)が必要になります。IdはId関数をつかってマッピングされ、引数がラムダ式で設定され、その戻り値から型が推定されます。例えば、対象クラスのIdプロパティがIntであれば、IDカラムはInt型となり、Guidであれば、Guid-Combとなりユニークな値が設定されるIDカラムとなります。
Idに設定する対象クラスのプロパティ名は"Id"である必要はなく、自由に定義できます。
逆に、対象クラスのIdプロパティをId以外のカラムにマッピングする場合はColumnメソッドを使用します。
たとえば、IdプロパティをデータベースのPersonIdにマッピングする場合は以下のように記述します。
Id(x => x.Id) .Column("PersonId") .GeneratedBy.Assigned();
さらにサンプルでは、GeneratedBy.Assigned()を使って、自動生成ではなく、すでにx.Idに設定された値を使用するように指示しています。
Properties
プロパティのマッピングは以下の通りです。
Map(x => x.FirstName);
Fluent NHibernateはプロパティの戻り値の型を知っており、プロパティと同じ名前でカラムを持つように動作します。また、Map関数につながる関数で多くのカスタマイズが利用可能です。例えば、Nullが可能なカラムを作成するにはNullable関数を、逆の場合はさらにNot関数を使うことが可能です。
// nullable Map(x => x.FirstName) .Nullable(); // not nullable Map(x => x.FirstName) .Not.Nullable();
リレーションシップ
非常に特殊な例を除いてエンティティ間でのリレーションをマッピングすると思います。一般的に、多対一、一対多、多対多の形態があり、それぞれ、Referrence、
HasMany、HasManyToManyで参照します。
Reference / many-to-one and HasMany / one-to-many
Reference関数は2つのエンテェティ間の多対一の作り、多側に使われます。後述しますが一側はHasMany関数を使用します。
例として、bookとauthorのリレーションシップで説明します。
- ブックは一人の著者を参照し
- 著者は複数のブックをもつ
という、前提で説明します。概念図は以下になります。
ER表記だと
と、なるでしょうか。実際にデータベーススキーマとして以下のようになることを想定します。
- bookテーブルにはauthorテーブルの主キーであるIdへの参照を持つ
- authorテーブルにはbookテーブルへの情報は持たない
この場合、マッピングされるクラス定義は以下のようになります。
注意点は
- BookクラスにはAuthorクラスへの参照として、Authorをメンバに持ちます
- Authorクラスは参照されていることをList<Book>としてメンバに持ちます
AuthorクラスのList<Book>で参照されていることを明示することがデータベーススキーマには無い概念なので特に注意してください。
AuthorテーブルはBookテーブルのことは知らなくても良かったのが、クラス間では知っていなければならなくなるということです。
クラス実装は以下のようになります。
namespace FluentNHibernateSample02.Entities { public class Book { public virtual int id { private set; get; } public virtual string title { set; get; } public virtual Author author { set; get; } } }
(Entities\Book.cs)
using System.Collections.Generic; namespace FluentNHibernateSample02.Entities { public class Author { public virtual int id { set; get; } public virtual string name { set; get; } public virtual IList<Book> books { set; get; } public Author() { books = new List<Book>(); } public virtual void AddBook(Book book) { book.author = this; books.Add(book); } } }
(Entities\Author.cs)
Authorクラスのbooks型はListではなく、IListでないとキャストエラーが発生するみたいなので注意してください。
引き続いて、マッピングクラスを実装します。
サンプル通りに1対多のマッピングを定義します。
using FluentNHibernate.Mapping; using FluentNHibernateSample02.Entities; namespace FluentNHibernateSample02.Mappings { public class BookMap : ClassMap<Book> { public BookMap() { Id(x => x.id); Map(x => x.title); References(x => x.author); } } }
(Mappings\BookMap.cs)
using FluentNHibernate.Mapping; using FluentNHibernateSample02.Entities; namespace FluentNHibernateSample02.Mappings { public class AuthorMap : ClassMap<Author> { public AuthorMap() { Id(x => x.id); Map(x => x.name); HasMany(x => x.books) .Inverse() .Cascade.All(); } } }
(Mappings\AuthorMap.cs)
では実際に、サンプルを実行させてみてください。
1. 環境構築を参照におさらいすると、
- サンプルプロジェクトを作成
- NuGetでFluent NHibernateをインストール
- npgsql参照追加(PostgreSQLの場合)
- Entitiesフォルダ、Mappingsフォルダを作成
- Bookクラス、AuthorクラスをEntitiesフォルダに実装
- BookMapクラス、AuthorMapクラスをMappingsフォルダに実装
- サンプルテストコード、Configurationを設定
- 実行
が、大まかな流れです。
以下に、簡単なサンプルテストコードとPostgreSQL用のConfigurationを載せておきます。
using System; using FluentNHibernateSample02.Entities; using FluentNHibernate.Cfg; using FluentNHibernate.Cfg.Db; using NHibernate; using NHibernate.Cfg; using NHibernate.Tool.hbm2ddl; using System.Collections.Generic; namespace FluentNHibernateSample02 { class Program { static void Main(string[] args) { var sessionFactory = CreateSessionFactory(); using (var session = sessionFactory.OpenSession()) { using (var transaction = session.BeginTransaction()) { // Idは設定の必要はありません var author1 = new Author { name = "宮部みゆき"}; var author2 = new Author { name = "東野圭吾"}; var book1 = new Book { title = "模倣犯" }; var book2 = new Book { title = "レベル7" }; var book3 = new Book { title = "流星の絆" }; author1.AddBook(book1); author1.AddBook(book2); author2.AddBook(book3); // Cascade.All()設定でBookエンテェティもストアされます session.SaveOrUpdate(author1); session.SaveOrUpdate(author2); transaction.Commit(); } // 読み出しコード using (session.BeginTransaction()) { var authors = session.CreateCriteria(typeof(Author)) .List(); foreach (Author author in authors) { WriteAuthorPretty(author); } } Console.ReadKey(); } } private static ISessionFactory CreateSessionFactory() { return Fluently.Configure() .Database(PostgreSQLConfiguration.PostgreSQL82 .ConnectionString(x => x .Host("localhost") .Port(5432) .Database("testdb2") .Username("postgres") .Password("postgres"))) .Mappings(m => m .FluentMappings.AddFromAssemblyOf<Program>()) .ExposeConfiguration(BuildSchema) .BuildSessionFactory(); } private static void BuildSchema(Configuration config) { new SchemaExport(config) .Create(false, true); } private static void WriteAuthorPretty(Author author) { Console.WriteLine("Author:"); Console.WriteLine(author.name); Console.WriteLine(" Books:"); foreach (var book in author.books) { Console.WriteLine(" " + book.title); } Console.WriteLine(); } } }
ビルド、実行すると下記の出力が得られ、データベースにデータがストアされていることが確認できます。
この時、Bookテーブル内のAuthorテーブルへの参照項目は自動的に author_id となっていること、またAuthor.idへのFK制約が追加されていることが解ります。
長くなったので、多対多以降は別ページで説明します。