摘要
從這一節起,介紹NHibernate Mapping的內容。前面文章都是使用的NHibernate XML Mapping。NHibernate XML Mapping是NHibernate最早最成熟的Mapping方法。其他的Mapping方法都是基於XML Mapping的思想進行的“變種”,要么暫時不能完全像XML Mapping那樣功能豐富。其他的Mapping方法目前包括:Fluent Mapping、Attribute Mapping和Mapping by Conventions。他們各自都有優缺點。使用者應該根據實際情況選擇適合自己項目的Mapping方式。
這篇文章介紹Fluent Mapping。本篇文章的代碼可以到Fluent NHibernate下載。
1、Fluent Mapping的優點
- Fluent Mapping提供了大量的Fluent API進行映射配置。相比XML Mapping,在代碼中進行配置能夠在編譯時發現很多問題。
- Fluent Mapping的可讀性更強,代碼更簡潔。
- Fluent Mapping將映射配置的類和實體映射類相分離,在一定程度上保持了實體類的簡潔性。
- Fluent Mapping使用Lamda表達式和靜態類型反射技術,不用寫大量常量字符串,避免了很多粗心的錯誤。
2、Fluent Mapping的缺點
- 在定義了實體類之后,需要另外定義一個實體映射類。
- 許多XML Mapping支持的功能,Fluent Mapping暫時不支持,需要等到Fluent Mapping新版本出來后才能支持。
- Fluent Mapping底層其實還是將代碼定義的映射翻譯成XML映射文件,因此在程序啟動的時候比XML Mapping稍慢。
- 如果數據庫表名稱和實體類名稱不一致,或者數據庫列名稱和屬性名稱不一致,還是需要用字符串的形式做映射,這基本是避免不了的。
3、程序演示
繼續使用以之前文章使用過的NHibernateDemoDB數據庫。
1)新建工程Demo.Fluent。

2)新建Class Library,名稱為Demo.Fluent.Entities。移除Class1.cs文件。

3)在新建的工程中,使用NuGet安裝FluentNHibernate。

單擊“Install”按鈕,會出現Priview對話框,列出將要添加的引用。安裝FluentNHibernate將會安裝他所依賴的NHibernate和Isesi.Collections。

點擊“OK”按鈕。等上幾分鍾時間去喝口茶, 安裝完成之后Output將顯示Finished。

展開工程的Reference,看到已經將FluentNHibernate添加進來了。

4)添加Domain文件夾和Mapping文件夾。
5)在Domain文件夾內,添加實體類的抽象泛型基類Entity。
1 namespace Demo.Fluent.Entities.Domain 2 { 3 public abstract class Entity<T> where T : Entity<T> 4 { 5 public virtual int Id { get; private set; } 6 7 public override bool Equals(object obj) 8 { 9 var other = obj as T; 10 if (other == null) return false; 11 var thisIsNew = Equals(Id, 0); 12 var otherIsNew = Equals(other.Id, 0); 13 if (thisIsNew && otherIsNew) 14 { 15 return ReferenceEquals(this, other); 16 } 17 return Id.Equals(other.Id); 18 } 19 20 private int? oldHashCode; 21 public override int GetHashCode() 22 { 23 // once we have a hashcode we'll never change it 24 if (oldHashCode.HasValue) 25 { 26 return oldHashCode.Value; 27 } 28 // when this instance is new we use the base hash code 29 // and remember it, so an instance can NEVER change its 30 // hash code. 31 var thisIsNew = Equals(Id, 0); 32 if (thisIsNew) 33 { 34 oldHashCode = base.GetHashCode(); 35 return oldHashCode.Value; 36 } 37 return Id.GetHashCode(); 38 } 39 40 public static bool operator ==(Entity<T> lhs, Entity<T> rhs) 41 { 42 return Equals(lhs, rhs); 43 } 44 public static bool operator !=(Entity<T> lhs, Entity<T> rhs) 45 { 46 return !Equals(lhs, rhs); 47 } 48 } 49 }
- 抽象基類Entity定義了實體類共有的Id屬性。
- 抽象基類Entity重寫了object類的Equals方法和GetHashCode方法,同時重載了運算符==和!=。
6)在Domain文件夾下,添加值對象類Address類、Name類,實體類:Customer類、Product類和Order類。
Address類
1 namespace Demo.Fluent.Entities.Domain 2 { 3 public class Address 4 { 5 public virtual string Street { get; set; } 6 public virtual string City { get; set; } 7 public virtual string Province { get; set; } 8 public virtual string Country { get; set; } 9 10 public bool Equals(Address other) 11 { 12 if (other == null) return false; 13 if (ReferenceEquals(this, other)) return true; 14 return Equals(other.Street, Street) && 15 Equals(other.City, City) && 16 Equals(other.Province, Province) && 17 Equals(other.Country, Country); 18 } 19 20 public override bool Equals(object obj) 21 { 22 return Equals(obj as Address); 23 } 24 25 public override int GetHashCode() 26 { 27 unchecked 28 { 29 var result = Street.GetHashCode(); 30 result = (result * 397) ^ (City != null ? City.GetHashCode() : 0); 31 result = (result * 397) ^ Province.GetHashCode(); 32 result = (result * 397) ^ Country.GetHashCode(); 33 return result; 34 } 35 } 36 } 37 }
Name類
1 using System; 2 3 namespace Demo.Fluent.Entities.Domain 4 { 5 public class Name 6 { 7 public string LastName { get; private set; } 8 public string FirstName { get; private set; } 9 10 public Name() { } 11 12 public Name(string firstName, string lastName) 13 { 14 if (string.IsNullOrWhiteSpace(firstName)) 15 { 16 throw new ArgumentException("First name must be defined."); 17 } 18 if (string.IsNullOrWhiteSpace(lastName)) 19 { 20 throw new ArgumentException("Last name must be defined."); 21 } 22 FirstName = firstName; 23 LastName = lastName; 24 } 25 26 public override int GetHashCode() 27 { 28 unchecked 29 { 30 var result = FirstName.GetHashCode(); 31 result = (result * 397) ^ LastName.GetHashCode(); 32 return result; 33 } 34 } 35 36 public bool Equals(Name other) 37 { 38 if (other == null) return false; 39 if (ReferenceEquals(this, other)) return true; 40 return Equals(other.FirstName, FirstName) && 41 Equals(other.LastName, LastName); 42 } 43 44 public override bool Equals(object other) 45 { 46 return Equals(other as Name); 47 } 48 } 49 }
Address和Name兩個類注意兩點:
- 重寫了object類的Equals方法和GetHashCode方法。
- 因為是值對象類型,因此不繼承Entity類。
Customer類
1 using System; 2 using System.Collections.Generic; 3 4 namespace Demo.Fluent.Entities.Domain 5 { 6 public class Customer : Entity<Customer> 7 { 8 public Customer() 9 { 10 MemberSince = DateTime.UtcNow; 11 } 12 13 public virtual Name Name { get; set; } 14 public virtual double AverageRating { get; set; } 15 public virtual int Points { get; set; } 16 public virtual bool HasGoldStatus { get; set; } 17 public virtual DateTime MemberSince { get; set; } 18 public virtual CustomerCreditRating CreditRating { get; set; } 19 public virtual Address Address { get; set; } 20 21 private readonly IList<Order> orders; 22 23 public virtual IList<Order> Orders 24 { 25 get 26 { 27 return orders; 28 } 29 } 30 } 31 32 public enum CustomerCreditRating 33 { 34 Excellent, VeryVeryGood, VeryGood, Good, Neutral, Poor, Terrible 35 } 36 }
這里有六個需要注意的地方:
- Customer類繼承泛型類Entity<Customer>。
- 不用再在Customer類里定義Id屬性。
- 必須有一個無參數的構造函數,可以在這個構造函數中定義屬性的默認值。
- 集合屬性類型必須定義成接口類型,Fluent NHibernate通過反射生成Fluent對應的集合類型。
- 不能在構造函數中對集合屬性進行初始化。
- 所有成員函數(如果有的話)和成員屬性都以virtual修飾。
Product類
1 using System.Collections.Generic; 2 3 namespace Demo.Fluent.Entities.Domain 4 { 5 public class Product : Entity<Product> 6 { 7 public virtual string ProductCode { get; set; } 8 9 public virtual string ProductName { get; set; } 10 11 public virtual string Description { get; set; } 12 13 private readonly IList<Order> orders; 14 15 public virtual IList<Order> Orders 16 { 17 get 18 { 19 return orders; 20 } 21 } 22 } 23 }
Order類
1 using System; 2 using System.Collections.Generic; 3 4 namespace Demo.Fluent.Entities.Domain 5 { 6 public class Order : Entity<Order> 7 { 8 public virtual DateTime Ordered { get; set; } 9 public virtual DateTime? Shipped { get; set; } 10 public virtual Address ShipTo { get; set; } 11 public virtual Customer Customer { get; set; } 12 13 private readonly IList<Product> products; 14 15 public virtual IList<Product> Products 16 { 17 get 18 { 19 return products; 20 } 21 } 22 } 23 }
7)在Mapping文件夾下,定義映射類AddressMap、NameMap、CustomerMap、ProductMap和OrderMap。
類名稱必須是值對象類型名稱或實體類名稱后面跟Map。
在映射類的無參構造函數內,調用Fluent NHibernate的API函數,定義映射。
AddressMap類
1 using Demo.Fluent.Entities.Domain; 2 using FluentNHibernate.Mapping; 3 4 namespace Demo.Fluent.Entities.Mapping 5 { 6 public class AddressMap : ComponentMap<Address> 7 { 8 public AddressMap() 9 { 10 Map(x => x.Street).Length(100); 11 Map(x => x.City).Length(100); 12 Map(x => x.Province).Length(100); 13 Map(x => x.Country).Length(100); 14 } 15 } 16 }
NameMap類
1 using Demo.Fluent.Entities.Domain; 2 using FluentNHibernate.Mapping; 3 4 namespace Demo.Fluent.Entities.Mapping 5 { 6 public class NameMap : ComponentMap<Name> 7 { 8 public NameMap() 9 { 10 Map(x => x.LastName).Not.Nullable().Length(10); 11 Map(x => x.FirstName).Not.Nullable().Length(10); 12 } 13 } 14 }
- Address類和Name類都時值對象類,因此他們的映射類文件都繼承ComponetMap的泛型類。
- Map、Not.Nullable、Length都時Fluent NHibernate的API函數(見名知意),通過鏈式調用,對單個屬性進行映射定義。
CustomerMap類
1 using Demo.Fluent.Entities.Domain; 2 using FluentNHibernate.Mapping; 3 4 namespace Demo.Fluent.Entities.Mapping 5 { 6 public class CustomerMap : ClassMap<Customer> 7 { 8 public CustomerMap() 9 { 10 Id(x => x.ID).GeneratedBy.Native(); 11 Component(x => x.Address); 12 Component(x => x.Name); 13 Map(x => x.Points); 14 Map(x => x.HasGoldStatus); 15 Map(x => x.MemberSince); 16 Map(x => x.CreditRating).CustomType<CustomerCreditRating>(); 17 HasMany(x => x.Orders).Inverse().Cascade.AllDeleteOrphan().Fetch.Join(); 18 } 19 } 20 }
- Customer類是實體類,繼承ClassMap的泛型類。
- Id方法定義主鍵屬性,調用Generate.Native()方法指出主鍵生成策略是indentity的。
- 對值對象類型的屬性,調用Component方法,定義映射。
- HasMany方法生成OneToManyPart對象,映射一對多關系。
- HasMany方法調用后面的一串方法:Cascade.AllDeleteOrphan().Fetch.Join()對應了XML映射響應的屬性。
關系映射的API方法:
一對一:HasOne
一對多:HasMany
多對對:HasManyToMany
ProductMap類
using Demo.Fluent.Entities.Domain; using FluentNHibernate.Mapping; namespace Demo.Fluent.Entities.Mapping { public class ProductMap : ClassMap<Product> { public ProductMap() { Id(x => x.ID).GeneratedBy.Native(); Map(x => x.ProductCode).Not.Nullable().Length(10); Map(x => x.ProductName).Not.Nullable().Length(50); Map(x => x.Description).Length(100); HasManyToMany(x => x.Orders).Table("ProductOrder").ParentKeyColumn("ProductId").ChildKeyColumn("OrderId").Cascade.AllDeleteOrphan(); } } }
- HasManyToMany方法生成ManyToManyPart對象,映射Many-to-Many關系。
- Table("ProductOrder").ParentKeyColumn("ProductId").ChildKeyColumn("OrderId")
- 上面連串方法調用定義了Many-to-Many映射的中間表表名。對於Product表,這個關系的主鍵列和外鍵列。
- 多對多關系映射默認生成的中間表的名稱是ProductToOrder。因此,這里要調用Table方法,用字符串定義中間表名稱。
OrderMap類
1 using FluentNHibernate.Mapping; 2 3 namespace Demo.Fluent.Entities.Domain 4 { 5 public class OrderMap : ClassMap<Order> 6 { 7 public OrderMap() 8 { 9 Table("`Order`"); 10 Id(x => x.ID).GeneratedBy.Native(); 11 Map(x => x.Ordered); 12 Map(x => x.Shipped); 13 Component(x => x.ShipTo); 14 References(x => x.Customer).Column("CustomerId").Cascade.SaveUpdate(); 15 HasManyToMany(x => x.Products).Table("ProductOrder").ParentKeyColumn("OrderId").ChildKeyColumn("ProductId").Cascade.All(); 16 } 17 } 18 }
- Table方法定義映射的表名稱,因為Order是SQL Server關鍵字,因此調用此方法,傳入字符串"`Order`"作為表名稱。生成的SQL語句的表名稱字符串是"[Order]"。
- Many-to-One關系,實體類屬性用Reference方法定義,指定外鍵列名稱。
- 在ProductMap類構造函數內已經定義了中間表名稱。因此,這里的Table("ProductOrder")可以省略。
8)添加用於測試的控制台應用程序Demo.Fluent.Console工程。
9)添加用於NHibernate設置的FluentConfig類。
1 using Demo.Fluent.Entities.Mapping; 2 using FluentNHibernate.Cfg; 3 using FluentNHibernate.Cfg.Db; 4 using NHibernate; 5 6 namespace Demo.Fluent.Console 7 { 8 class FluentConfig 9 { 10 const string connString = "server=localhost;" + "database=NHibernateDemoDB;" + "integrated security=SSPI;"; 11 12 public static ISessionFactory CreateSessionFactory() 13 { 14 return Fluently.Configure() 15 .Database(MsSqlConfiguration.MsSql2008.ConnectionString(connString)) 16 .Mappings(m => m.FluentMappings.AddFromAssemblyOf<CustomerMap>()) 17 .BuildSessionFactory(); 18 } 19 } 20 }
9)修改Program類。
1 using Demo.Fluent.Entities.Domain; 2 using NHibernate.Linq; 3 using System.Linq; 4 5 namespace Demo.Fluent.Console 6 { 7 class Program 8 { 9 static void Main(string[] args) 10 { 11 var factory = FluentConfig.CreateSessionFactory(); 12 using (var session = factory.OpenSession()) 13 { 14 var customer = session.Get<Customer>(2); 15 System.Console.WriteLine("{0} {1}", customer.Name.LastName, customer.Name.FirstName); 16 17 System.Console.WriteLine("order count: {0}",customer.Orders.Count()); 18 19 System.Console.WriteLine(); 20 System.Console.WriteLine("customers and their order count:"); 21 var queryCount = session.Query<Customer>().Select(c => new 22 { 23 CustomerId = c.Id, 24 CustomerName = c.Name.FirstName + " " + c.Name.LastName, 25 Count = c.Orders.Count() 26 }); 27 var listCount = queryCount.ToList(); 28 if (listCount.Count > 0) 29 { 30 listCount.ForEach(o => 31 { 32 System.Console.WriteLine("{0}-{1}: {2}", o.CustomerId, o.CustomerName, o.Count); 33 }); 34 } 35 36 System.Console.WriteLine(); 37 38 System.Console.WriteLine("customers whose oders count greater than 2:"); 39 var queryCountGreater = session.Query<Customer>().Where(c => c.Orders.Count > 2); 40 var listCountGreater = queryCountGreater.ToList(); 41 if (listCountGreater.Count > 0) 42 { 43 listCountGreater.ForEach(o => 44 { 45 System.Console.WriteLine("{0}-{1} {2}", o.Id, o.Name.FirstName, o.Name.LastName); 46 }); 47 } 48 } 49 System.Console.WriteLine(); 50 System.Console.WriteLine("Finished"); 51 System.Console.ReadLine(); 52 } 53 } 54 }
這里寫了三個查詢用來測試。第一個查詢是通過Id查找Customer對象。第二個查詢使用Linq to NHibernate對Customer和訂單數量分組查詢。第三個查詢查找訂單數大於2的Customer信息。
執行程序,得到結果(與數據庫記錄有關)。

結語
雖然Fluent NHibernate目前還不是很成熟(比起XML Mapping來說),但是絕大部分Mapping功能都已經可以能滿足了。前面提過了他的優缺點,有興趣的可以到Fluent NHibernate官網http://www.fluentnhibernate.org上去查看更詳細的內容。
