最近在做項目的時候用到了NHibernate,使用它并不困難,但是很麻煩。如果我的資料庫有幾百張表如果想要一個個的映射豈不是很麻煩,是以這種情況下使用NHibernate就會很笨重,雖然這個ORM架構本身功能強大,但屬于重量級的在使用的時候太笨重了,是以需要在項目中改良。這時候就應運而生了FluentNHibernate,它是流暢版的NHibernate,支援所有的NHibernate功能,而且還封裝了配置檔案的映射功能,也就是說可以将映射使用C#代碼編寫,這樣在維護時就會很簡單。
在沒有FluentNHibernate的情況下,如果使用NHibernate來做資料庫映射,那麼首先需要安裝NHibernate(也就是應用Nhibernate.dll),然後建立Nhibernate.cfg.xml資料庫配置檔案,然後建立映射檔案.xml,最後建立Session,直接對對象操作即可。雖然這樣做并不困難,但是很麻煩,想象下如果資料庫表有上百張,那使用這種方法映射不就很麻煩,笨重了嗎。
那麼FluentNHibernate有什麼好處呢,它能夠省略建立映射檔案.xml,使用C#代碼編寫映射檔案,這樣做能在一定情況下簡化工作量,同時也便于對映射代碼進行修改,具體使用方法接下來會詳細讨論。
一、建立資料庫配置檔案
首先建立一個資料庫的配置檔案,剛開始使用的話手動編寫太麻煩,這時候可以考慮使用自帶的配置檔案,在官網下載下傳後會有一個名為Configuration_Templates的檔案夾,裡面有不同資料庫的配置檔案,可以使用它的預設設定,但是需要将名稱改為Nhibernate.cfg.xml。這裡使用如下的配置:
[html] view plain copy print ?
- <?xml version="1.0" encoding="utf-8"?>
- <!-- This is the System.Data.dll provider for SQL Server -->
- <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
- <session-factory name="KaddzvoteNHibernateFactory">
- <property name="connection.driver_class">
- NHibernate.Driver.SqlClientDriver
- </property>
- <property name="connection.connection_string">
- Data Source=.;Initial Catalog=Mapping;Integrated Security=true;Pooling=True;Min Pool Size=20;Max Pool Size=60
- </property>
- <property name="dialect">
- NHibernate.Dialect.MsSql2005Dialect
- </property>
- <property name="current_session_context_class">thread_static</property>
- <property name="generate_statistics">true</property>
- <property name="proxyfactory.factory_class">NHibernate.Bytecode.DefaultProxyFactoryFactory, NHibernate</property>
- <property name="query.substitutions">
- true 1, false 0, yes 'Y', no 'N'
- </property>
- <!--配置是否顯示sql語句,true代表顯示-->
- <property name="show_sql">true</property>
- </session-factory>
- </hibernate-configuration>
<?xml version="1.0" encoding="utf-8"?>
<!-- This is the System.Data.dll provider for SQL Server -->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory name="KaddzvoteNHibernateFactory">
<property name="connection.driver_class">
NHibernate.Driver.SqlClientDriver
</property>
<property name="connection.connection_string">
Data Source=.;Initial Catalog=Mapping;Integrated Security=true;Pooling=True;Min Pool Size=20;Max Pool Size=60
</property>
<property name="dialect">
NHibernate.Dialect.MsSql2005Dialect
</property>
<property name="current_session_context_class">thread_static</property>
<property name="generate_statistics">true</property>
<property name="proxyfactory.factory_class">NHibernate.Bytecode.DefaultProxyFactoryFactory, NHibernate</property>
<property name="query.substitutions">
true 1, false 0, yes 'Y', no 'N'
</property>
<!--配置是否顯示sql語句,true代表顯示-->
<property name="show_sql">true</property>
</session-factory>
</hibernate-configuration>
二、建立實體
NHibernate的基本映射和Hibernate是完全相同的,有關基本的映射這裡不再詳細的讨論,可以翻閱筆者的前幾篇文章。下面自己做了一個小的項目Demo,示範如何使用NHibernate建立一個資料庫的映射,具體的資料庫結構圖如下:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiclRnblN0LclHdpZXYyd2LcBzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CXxw2RlVHbHVmZk1mYohWblZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39TMyQjMwITMzIDNyITM0EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
上圖的資料庫結構圖中涵蓋了基本的映射關系,在實際的項目中也就是上面出現的幾種基本的關系,其中涵蓋了一對一、多對一、多對多的關聯關系,接下來将會使用FluentNHibernate來實作基本的映射關系。
2.1 建立實體
添加完配置檔案後使用第三方工具将資料庫表導出為實體對象,也就是添加資料庫表的實體對象。添加完成後繼續添加資料庫的映射類,添加映射類時需要繼承NHibernate的ClassMap<T>類,将資料庫實體放置到對象内部,這樣在映射時能夠直接使用,它使用的是泛型來實作的。資料庫表的實體如下代碼:
[csharp] view plain copy print ?
- using System;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- using FluentNHibernate.Automapping;
- using FluentNHibernate.Conventions;
- using NHibernate;
- using NHibernate.Collection.Generic;
- namespace ClassLibrary1.mapping
- {
- public abstract class Entity
- {
- virtual public int ID { get; set; }
- }
- public class User : Entity
- {
- virtual public string Name { get; set; }
- public virtual string No { get; set; }
- public virtual UserDetails UserDetails { get; set; }
- }
- public class Project : Entity
- {
- public Project()
- {
- Task=new List<Task>();
- Product=new List<Product>();
- }
- public virtual string Name { get; set; }
- public virtual User User { get; set; }
- public virtual IList<Product> Product { get; set; }
- public virtual IList<Task> Task{get;protected set; }
- }
- public class Product : Entity
- {
- public Product()
- {
- Project=new List<Project>();
- }
- public virtual IList<Project> Project { get; set; }
- public virtual string Name { get; set; }
- public virtual string Color { get; set; }
- }
- public class Task : Entity
- {
- public virtual string Name { get; set; }
- public virtual Project Project { get; set; }
- }
- public class UserDetails : Entity
- {
- public virtual User User { get; set; }
- public virtual int Sex { get; set; }
- public virtual int Age { get; set; }
- public virtual DateTime BirthDate { get; set; }
- public virtual decimal Height { get; set; }
- }
- }
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FluentNHibernate.Automapping;
using FluentNHibernate.Conventions;
using NHibernate;
using NHibernate.Collection.Generic;
namespace ClassLibrary1.mapping
{
public abstract class Entity
{
virtual public int ID { get; set; }
}
public class User : Entity
{
virtual public string Name { get; set; }
public virtual string No { get; set; }
public virtual UserDetails UserDetails { get; set; }
}
public class Project : Entity
{
public Project()
{
Task=new List<Task>();
Product=new List<Product>();
}
public virtual string Name { get; set; }
public virtual User User { get; set; }
public virtual IList<Product> Product { get; set; }
public virtual IList<Task> Task{get;protected set; }
}
public class Product : Entity
{
public Product()
{
Project=new List<Project>();
}
public virtual IList<Project> Project { get; set; }
public virtual string Name { get; set; }
public virtual string Color { get; set; }
}
public class Task : Entity
{
public virtual string Name { get; set; }
public virtual Project Project { get; set; }
}
public class UserDetails : Entity
{
public virtual User User { get; set; }
public virtual int Sex { get; set; }
public virtual int Age { get; set; }
public virtual DateTime BirthDate { get; set; }
public virtual decimal Height { get; set; }
}
}
三、映射詳解
在添加映射檔案時需要繼承FluentNHibernate的ClassMap<T>類,然後在類的構造函數中添加映射的方法,具體的方法是使用的lamuda表達式來映射的,方法的名稱跟配置檔案的名稱基本一緻,書寫也很友善,接下來将會拆分映射來詳細講解FluentNHibernate的Mapping使用方法。
3.1 一對一映射
首先來看看一對一的映射關系,使用者和使用者資訊表在實際中是一對一的關系,這兩個表之間是通過使用UserID來互相關聯的,它們有一個共同的ID,在插入Users表的同時也要寫入UserDetails表,是以需要添加一對一的限制關系,具體的在Users和UsersDetails兩表的映射方法如下代碼:
[csharp] view plain copy print ?
- public class UsersMapping : ClassMap<User>
- {
- public UsersMapping()
- {
- Table("Users");
- LazyLoad();
- Id(x => x.ID).Column("UserID").GeneratedBy.Identity();
- HasOne(x => x.UserDetails).Cascade.All().PropertyRef("User");
- Map(x => x.Name).Nullable();
- Map(x => x.No).Nullable();
- }
- }
public class UsersMapping : ClassMap<User>
{
public UsersMapping()
{
Table("Users");
LazyLoad();
Id(x => x.ID).Column("UserID").GeneratedBy.Identity();
HasOne(x => x.UserDetails).Cascade.All().PropertyRef("User");
Map(x => x.Name).Nullable();
Map(x => x.No).Nullable();
}
}
Note:FluentNHibernate在映射時有很多種映射方法,比如Cascade它是指該對象在進行操作時關聯到的子對象的操作類型,上面指定了All說明所有的操作都會關聯到子表,還有SaveUpdate在添加和更新時關聯子表,另外還有None類型不推薦使用此類型因為會出現很多問題。
UserDetails的映射中的主鍵ID是繼承自User類的是以在指定ID時需要添加Foreign外鍵關聯的屬性名,内部的參數一定要是UserDetails的屬性名。
[csharp] view plain copy print ?
- public class UserDetailsMapping : ClassMap<UserDetails>
- {
- public UserDetailsMapping()
- {
- Table("UserDetails");
- LazyLoad();
- Id(x => x.ID).Column("UserID").GeneratedBy.Foreign("User");
- Map(x => x.Height).Nullable();
- Map(x => x.Age).Nullable();
- Map(x => x.Sex).Nullable();
- Map(x => x.BirthDate).Nullable();
- HasOne(x => x.User).Cascade.All();
- }
- }
public class UserDetailsMapping : ClassMap<UserDetails>
{
public UserDetailsMapping()
{
Table("UserDetails");
LazyLoad();
Id(x => x.ID).Column("UserID").GeneratedBy.Foreign("User");
Map(x => x.Height).Nullable();
Map(x => x.Age).Nullable();
Map(x => x.Sex).Nullable();
Map(x => x.BirthDate).Nullable();
HasOne(x => x.User).Cascade.All();
}
}
使用測試方法檢視映射結果,具體的測試方法如下:
[csharp] view plain copy print ?
- using System;
- using System.Collections.Generic;
- using ClassLibrary1.mapping;
- using FluentNHibernate.Testing;
- using ClassLibrary1;
- using NHibernate;
- using NUnit.Framework;
- namespace UnitTestProject1
- {
- [TestFixture]
- public class UnitTest1:NHConfig
- {
- [Test]
- public void TestUsers_UserDetails()
- {
- //get user from database
- User user1 = Session.Load<User>(1);
- //save the User data
- Session.Transaction.Begin();
- User user=new User()
- {
- Name = "Jack",
- No = "12321"
- };
- UserDetails userDetails=new UserDetails()
- {
- Age = 12,
- BirthDate = DateTime.Now.Date,
- Height = 240,
- Sex = 1
- };
- user.UserDetails = userDetails;
- userDetails.User = user;
- Session.Save(user);
- Session.Transaction.Commit();
- }
- }
- }
using System;
using System.Collections.Generic;
using ClassLibrary1.mapping;
using FluentNHibernate.Testing;
using ClassLibrary1;
using NHibernate;
using NUnit.Framework;
namespace UnitTestProject1
{
[TestFixture]
public class UnitTest1:NHConfig
{
[Test]
public void TestUsers_UserDetails()
{
//get user from database
User user1 = Session.Load<User>(1);
//save the User data
Session.Transaction.Begin();
User user=new User()
{
Name = "Jack",
No = "12321"
};
UserDetails userDetails=new UserDetails()
{
Age = 12,
BirthDate = DateTime.Now.Date,
Height = 240,
Sex = 1
};
user.UserDetails = userDetails;
userDetails.User = user;
Session.Save(user);
Session.Transaction.Commit();
}
}
}
在get和save對象的地方添加斷點,Debug運作測試就會看到執行的結果。
3.2 一對多/多對一
一對多和多對一是相對而言的正如上例中的Projects和Tasks類似,一個Projects有多個Tasks,反過來說就是多個Tasks可能會對應一個Projects是以有時一對多的關系也就是多對一的關系,隻不過是一種特殊的多對一。這裡的多對一比較特殊,常見的多對一的關系比如學生和班級的關系,多個學生屬于一個班級。
一對多的映射方法和一對一的映射方法其實很多地方是類似的,隻不過一對多的關系裡面要添加一個外鍵引用關系,然後在多的一端添加一個外鍵,在一的一端添加HasMany,映射到Projects和Tasks中就是在Tasks(多)中添加Project的外鍵。
3.2.1 映射
首先時Tasks表的映射,因為Tasks表是多的一端,是以要添加對Projects表的外鍵引用關系,另外因為是一種外鍵引用不關系到父表的操作,是以這裡可以使用Cascade.None()。
[csharp] view plain copy print ?
- public class TasksMappping : ClassMap<ClassLibrary1.mapping.Task>
- {
- public TasksMappping()
- {
- Table("Tasks");
- LazyLoad();
- Id(x => x.ID).Column("TaskID").GeneratedBy.Identity();
- References(x => x.Project).Nullable().Column("ProjectID").Cascade.None();
- Map(x => x.Name).Nullable();
- }
- }
public class TasksMappping : ClassMap<ClassLibrary1.mapping.Task>
{
public TasksMappping()
{
Table("Tasks");
LazyLoad();
Id(x => x.ID).Column("TaskID").GeneratedBy.Identity();
References(x => x.Project).Nullable().Column("ProjectID").Cascade.None();
Map(x => x.Name).Nullable();
}
}
在Projects表中,因為該表中的一個ID會對應多個Tasks是以在添加HasMany方法,來表明Projects和Tasks的多對一的關系,如下代碼它會涉及到任務的添加和更新操作,是以需要使用Cascade.SaveUpdate()。
[csharp] view plain copy print ?
- public class ProjectsMapping:ClassMap<Project>
- {
- public ProjectsMapping()
- {
- Table("Projects");
- LazyLoad();
- Id(x => x.ID).Column("ProjectID").GeneratedBy.Identity();
- References(x => x.User).Column("UserID").Cascade.None();
- Map(x => x.Name).Nullable();
- HasMany(x => x.Task).KeyColumn("ProjectID").LazyLoad().Cascade.SaveUpdate();
- }
- }
public class ProjectsMapping:ClassMap<Project>
{
public ProjectsMapping()
{
Table("Projects");
LazyLoad();
Id(x => x.ID).Column("ProjectID").GeneratedBy.Identity();
References(x => x.User).Column("UserID").Cascade.None();
Map(x => x.Name).Nullable();
HasMany(x => x.Task).KeyColumn("ProjectID").LazyLoad().Cascade.SaveUpdate();
}
}
Note:TasksMapping中的外鍵關系使用的是Cascade.None這說明它的操作不會涉及到Projects的操作,但是ProjectsMapping中使用了Cascade.SaveUpdate()方法是以在save或者update Projects的時候會連帶着修改Tasks。
3.2.2 Unit Test
編寫單元測試代碼如下:
[csharp] view plain copy print ?
- [Test]
- public void TestOneToMany()
- {
- Project project = Session.Get<Project>(15);
- //save the User data
- Session.Transaction.Begin();
- Task task = new Task()
- {
- Name ="create",
- Project = project
- };
- Session.Save(task);
- Session.Transaction.Commit();
- Task task1 = Session.Get<Task>(1);
- }
[Test]
public void TestOneToMany()
{
Project project = Session.Get<Project>(15);
//save the User data
Session.Transaction.Begin();
Task task = new Task()
{
Name ="create",
Project = project
};
Session.Save(task);
Session.Transaction.Commit();
Task task1 = Session.Get<Task>(1);
}
執行檢視結果:
這裡使用的一對多的關聯隻是單向的關聯,在關聯中不僅有單向的另外還有雙向關聯,具體使用方法這裡不再詳細讨論,有興趣學習的話可以翻閱筆者的前篇文章有關Hibernate的關聯關系。
3.3 多對多
上文詳細讨論了一對一、多對一/一對多的關系,使用FluentNHibernate來映射這種關系就很簡單了,最後繼續讨論多對多的關系,多對多的關系在使用的時候更類似于一對一的關系,因為它屬于雙向的關聯,是以需要在關聯的兩端同時添加HasManyToMany的映射方法,另外反應到資料庫中這其實是需要建立關聯表,利用第三張表來維護雙向的關系,具體的使用方法如下執行個體。
3.3.1 映射
在項目中常見的多對多的關系有很多,比如本例中使用的Product和Project的關系,一個Project會有有很多Product,同時一個Product也可能會在多個Project中,它們之間就形成了多對多的關聯關系。反映到映射關系中,代碼如下:
[csharp] view plain copy print ?
- public class ProjectsMapping:ClassMap<Project>
- {
- public ProjectsMapping()
- {
- Table("Projects");
- LazyLoad();
- Id(x => x.ID).Column("ProjectID").GeneratedBy.Identity();
- References(x => x.User).Column("UserID").Cascade.None();
- Map(x => x.Name).Nullable();
- HasMany(x => x.Task).KeyColumn("ProjectID").LazyLoad().Cascade.SaveUpdate();
- HasManyToMany(x => x.Product).ParentKeyColumn("ProjectID").ChildKeyColumn("ProductID").Table("ProjectProduct");
- }
- }
- public class ProductMapping : ClassMap<Product>
- {
- public ProductMapping()
- {
- Table("Product");
- Id(x => x.ID).Column("ProductID").GeneratedBy.Identity();
- Map(x => x.Name).Nullable();
- Map(x => x.Color).Nullable();
- HasManyToMany(x => x.Project).ParentKeyColumn("ProductID").ChildKeyColumn("ProjectID").Table("ProjectProduct");
- }
- }
public class ProjectsMapping:ClassMap<Project>
{
public ProjectsMapping()
{
Table("Projects");
LazyLoad();
Id(x => x.ID).Column("ProjectID").GeneratedBy.Identity();
References(x => x.User).Column("UserID").Cascade.None();
Map(x => x.Name).Nullable();
HasMany(x => x.Task).KeyColumn("ProjectID").LazyLoad().Cascade.SaveUpdate();
HasManyToMany(x => x.Product).ParentKeyColumn("ProjectID").ChildKeyColumn("ProductID").Table("ProjectProduct");
}
}
public class ProductMapping : ClassMap<Product>
{
public ProductMapping()
{
Table("Product");
Id(x => x.ID).Column("ProductID").GeneratedBy.Identity();
Map(x => x.Name).Nullable();
Map(x => x.Color).Nullable();
HasManyToMany(x => x.Project).ParentKeyColumn("ProductID").ChildKeyColumn("ProjectID").Table("ProjectProduct");
}
}
具體添加關聯的步驟如下:
(1)在映射的兩端同時添加HasManyToMany的關系這樣就形成了雙向的關聯關系
(2)指定映射的ParentKey和ChildKey,一般會将對象本身的ID指定為ParentKey,關聯對象的ID指定為ChildKey
(3)指定關聯關系的關系表,使用Table方法指定關聯表,如上示例的Table("ProjectProduct")。
3.3.2 Unit Test
最後添加一個測試方法來檢視映射的結果,是否實作了多對多的映射關系,具體測試方法如下。
[csharp] view plain copy print ?
- [Test]
- public void TestManyToMany()
- {
- Session.Transaction.Begin();
- //get the Project
- ICriteria query = Session.CreateCriteria<Project>();
- IList<Project> projects = query.List<Project>();
- //create the Product
- Product product=new Product()
- {
- Name = "Product1",
- Color = "Red"
- };
- product.Project = projects;
- Session.Save(product);
- Session.Transaction.Commit();
- }
[Test]
public void TestManyToMany()
{
Session.Transaction.Begin();
//get the Project
ICriteria query = Session.CreateCriteria<Project>();
IList<Project> projects = query.List<Project>();
//create the Product
Product product=new Product()
{
Name = "Product1",
Color = "Red"
};
product.Project = projects;
Session.Save(product);
Session.Transaction.Commit();
}
Debug運作測試方法,運作到get projects處檢視projects所擷取的對象資訊如下圖:
從上圖可以看出已經擷取到了與projects相關聯的Product,這就是多對多的關系,在擷取projects時同時擷取了與它關聯的Products,如果這裡使用Lazyload方式的話就不會擷取所有的資訊,是以要根據具體的情況而定。
繼續運作測試,成功執行。
檢視資料庫發現資料已經成功添加,如下圖:
結語
本文主要讨論了FluentNHibernate的基本使用技巧,突出讨論了一對一的雙向關聯映射,一對多的單向關聯和多對多的雙向關聯關系,它們使用相當簡單,因為有了FluentNHibernate,隻需要了解關聯的規則就可以了,從資料模型到對象模型真的就很簡單了。
雖然使用FluentNHibernate在一定程度上減少了編寫代碼,但是并不能真正的解決代碼備援的繁瑣問題,可否有一中不需要編寫Mapping代碼的方法來實作映射關系呢?是的FluentNHibernate還封裝了一種AutoMapping方式來映射對象,是一種自動映射的方法,隻需要繼承實作資料庫表到對象的轉換規則就可以了,具體的實作方法将會在下篇文章中詳細讨論。