NHibernate系列文章二十七:NHibernate Mapping之Fluent Mapping基礎(附程序下載)

摘要數據庫

從這一節起,介紹NHibernate Mapping的內容。前面文章都是使用的NHibernate XML Mapping。NHibernate XML Mapping是NHibernate最先最成熟的Mapping方法。其餘的Mapping方法都是基於XML Mapping的思想進行的「變種」,要麼暫時不能徹底像XML Mapping那樣功能豐富。其餘的Mapping方法目前包括:Fluent Mapping、Attribute Mapping和Mapping by Conventions。他們各自都有優缺點。使用者應該根據實際狀況選擇適合本身項目的Mapping方式。session

這篇文章介紹Fluent Mapping。本篇文章的代碼能夠到Fluent NHibernate下載。app

一、Fluent Mapping的優勢iview

  • Fluent Mapping提供了大量的Fluent API進行映射配置。相比XML Mapping,在代碼中進行配置可以在編譯時發現不少問題。
  • Fluent Mapping的可讀性更強,代碼更簡潔。
  • Fluent Mapping將映射配置的類和實體映射類相分離,在必定程度上保持了實體類的簡潔性。
  • Fluent Mapping使用Lamda表達式和靜態類型反射技術,不用寫大量常量字符串,避免了不少粗心的錯誤。

二、Fluent Mapping的缺點ide

  • 在定義了實體類以後,須要另外定義一個實體映射類。
  • 許多XML Mapping支持的功能,Fluent Mapping暫時不支持,須要等到Fluent Mapping新版本出來後才能支持。
  • Fluent Mapping底層其實仍是將代碼定義的映射翻譯成XML映射文件,所以在程序啓動的時候比XML Mapping稍慢。
  • 若是數據庫表名稱和實體類名稱不一致,或者數據庫列名稱和屬性名稱不一致,仍是須要用字符串的形式作映射,這基本是避免不了的。

三、程序演示函數

繼續使用以以前文章使用過的NHibernateDemoDB數據庫。測試

1)新建工程Demo.Fluent。ui

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

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

單擊「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上去查看更詳細的內容。

相關文章
相關標籤/搜索