映射文件,用於告訴NHibernate數據庫里的表、列於.Net程序中的類的關系。因此映射文件的配置非常重要。
一、一對一
NHibernate一對一關系的配置方式使用<one-to-one>配置節點。
當我們兩個表擁有相同的主鍵字段,主鍵值相同的需要關聯在一起。比較典型的一個例子是,一個對象的屬性太多,常用的和不常用的分開存放。例如一個文章表,我們將文章內容字段,提取出來作為一個單獨的字段,因為比較長。
下面我們來新建兩張表如下:
本來, Article表還有很多字段,比如添加時間,所屬欄目,是否高亮,是否置頂等等,但是本處僅僅做示范NHibernate一對多一配置之使用。因此簡略了。
Article.hbm.xml
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"> <class name="Model.ArticleModel,Model" table="Article"> <id name="Id" column="ArticleId" type="Int32"> <generator class="native"/> </id> <one-to-one name="Content" cascade="all" /> <property name="Title" column="ArticleTitle" type="String"/> </class> </hibernate-mapping>
Content.hbm.xml
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"> <class name="Model.ContentModel,Model" table="Content"> <id name="Id" column="ArticleId" type="Int32"> <generator class="native"/> </id> <one-to-one name="Article" cascade="all" /> <property name="Content" column="ArticleContent" type="String"/> </class> </hibernate-mapping>
ArticleModel.cs
public class ArticleModel { public virtual int Id { get; set; } public virtual string Title { get; set; } public virtual ContentModel Content { get; set; } }
ContentModel.cs
public class ContentModel { public virtual int Id { get; set; } public virtual string Content { get; set; } public virtual ArticleModel Article { get; set; } }
Program.cs
class Program { static void Main(string[] args) { ISessionFactory sessionFactory = new Configuration().Configure().BuildSessionFactory(); using (ISession session = sessionFactory.OpenSession()) { ArticleModel art = session.Get<ArticleModel>(1); Console.WriteLine(art.Id); Console.WriteLine(art.Title); Console.WriteLine(art.Content.Content); } Console.ReadKey(); } }
輸出結果如下:
2016-05-16:今天對一對一又有新理解。
像上面這種主鍵關聯一對一,可以設置兩種方式。
<one-to-one name="ArtContent" cascade="none" constrained="true"/> 對象1配置方式 <one-to-one name="Article" cascade="all" constrained="true" /> 對象2配置方式
這樣設置有什么好處呢?
1、cascade="none"那個,是不關聯。不需要關聯的操作,選擇上面這個對象來Add,Select,Update。SELECT是個嚴重的問題,數據多了以后,如果不需要Join的情況下Join了,資源浪費,很慢。
2、cascade="all",需要關聯的操作,使用下面這個對象來操作。
之前弄錯了,配置成這樣。
<one-to-one name="ArtContent" /> 對象1配置方式 <one-to-one name="Article" cascade="all" constrained="true" /> 對象2配置方式
因為,如果你什么關聯都不寫,有可能會是級聯操作,即使你不希望關聯,NHibernate也會關聯,查詢的時候默認關聯查詢出了你不需要的對象。所以會造成資源浪費的問題。
當然,整個NHibernate外層還可以配置默認的default-cascade關聯操作。
有時間還是要多跟跟SQL語句,才能明白怎樣配置才是最優的。
二、一對多
來看以下兩張表,這是一個典型的一對多關系。人與國家:
Country表:
Person表:
NHibernate映射文件配置基礎,一對多配置示例。先來看看映射文件:
Country.hbm.xml
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"> <class name="Model.CountryModel, Model" table="Country"> <id name="CountryId" column="CountryId" type="Int32"> <generator class="native"/> </id> <property name="CountryName" column="CountryName" type="String"/> <!-- 一個Country里面有多個Person --> <set name="ListPerson" table="Person" generic="true" inverse="true"> <key column="CountryId" foreign-key="FK_Person_Country"/> <one-to-many class="Model.PersonModel,Model"/> </set> </class> </hibernate-mapping>
Person.hbm.xml
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"> <class name="Model.PersonModel, Model" table="Person"> <id name="PersonId" column="PersonId" type="Int32"> <generator class="native"/> </id> <property name="PersonName" column="PersonName" type="String"/> <!--多對一關系:Person屬於一個Country name是Person實體類里的--> <many-to-one name="Country" column="CountryId" not-null="true" class="Model.CountryModel,Model" foreign-key="FK_Person_Country" /> </class> </hibernate-mapping>
CountryModel.cs
namespace Model { public class CountryModel { public virtual int CountryId { get; set; } public virtual string CountryName { get; set; } //N個PersonModel屬於一個CountryModel public virtual ISet<PersonModel> ListPerson { get; set; } } }
PersonModel.cs
namespace Model { public class PersonModel { public virtual int PersonId { get; set; } public virtual string PersonName { get; set; } //要注意到一個PersonModel是屬於一個CountryModel public virtual CountryModel Country { get; set; } } }
這里要說明以下,NHibernate巧妙地通過集合與實體解決了一對多、多對一關系。將延遲加載發揮到極限。
CountryDao.cs:
namespace Dao { public class CountryDao {public IList<CountryModel> GetCountyList() { ISession NSession = NHibernateHelper.GetSession(); return NSession.QueryOver<CountryModel>().List(); } } }
PersonDao.cs:
namespace Dao { public class PersonDao {public IList<PersonModel> GetPersonList() { ISession NSession = NHibernateHelper.GetSession(); return NSession.QueryOver<PersonModel>().List(); } } }
Program.cs:
class Program { static void Main(string[] args) { PersonDao pDao = new PersonDao(); IList<PersonModel> ListPerson = pDao.GetPersonList(); foreach (PersonModel p in ListPerson) {
//輸出Person所屬的國家名 Console.WriteLine(p.PersonId + " " + p.PersonName + " " + p.Country.CountryName); } CountryDao cDao = new CountryDao(); IList<CountryModel> ListCountry = cDao.GetCountyList(); foreach (CountryModel m in ListCountry) { Console.WriteLine(m.CountryName + ":");
//循環輸出該國家的所有人名 foreach (PersonModel p1 in m.ListPerson) { Console.WriteLine("--" + p1.PersonName); } } Console.ReadKey(); } }
輸出結果如下:
雖然你沒有寫過一句join,但是你直接就能夠"."出來了相關的東西,感覺NHibernate非常強大。不過方便歸方便,SQL語句可不能忘。
延遲加載小嘗甜頭
下面來玩點有趣的東西,在Dao里加如下一個方法:
public PersonModel GetPerson() { ISession NSession = NHibernateHelper.GetSession(); return NSession.Get<PersonModel>(1); }
Program.cs主程序改為如下:
static void Main(string[] args) { PersonDao dao = new PersonDao(); PersonModel p = dao.GetPerson(); Console.WriteLine(p.PersonId); Console.WriteLine(p.PersonName); Thread.Sleep(5000); //停止5秒鍾后,再輸出個人所屬國家名 Console.WriteLine(p.Country.CountryName); }
對於輸出結果我不關注,我關注的是NHibernate對SQLServer做了什么,我們來看看SQL Server Profiler監控到了什么?
留意兩條SQL語句的執行時間間隔,剛好是5秒,那么sql語句是什么呢?
exec sp_executesql N'SELECT personmode0_.PersonId as PersonId0_0_, personmode0_.PersonName as PersonName0_0_, personmode0_.CountryId as CountryId0_0_ FROM Person personmode0_ WHERE personmode0_.PersonId=@p0',N'@p0 int',@p0=1 --第一條:等價於 SELECT PersonId,PersonName,CountryId FROM Person WHERE PersonId = 1 exec sp_executesql N'SELECT countrymod0_.CountryId as CountryId1_0_, countrymod0_.CountryName as CountryN2_1_0_ FROM Country countrymod0_ WHERE countrymod0_.CountryId=@p0',N'@p0 int',@p0=1 --第二條:等價於 SELECT CountryId,CountryName FROM WHEE CountryId = 1
留意到NHibernate並沒有采用inner join的語法,將Country的數據也一並從數據庫讀到程序中,而是當C#5秒后要用到CountryName這個東西的時候,它才去數據庫讀取。很明顯的結論,如果C#不打算輸出CountryName,NHibernate根本不會執行第二條SQL語句。
延遲加載 lazy:true
下面再來一點點變種,在Country.hbm.xml映射文件里的第一行加上一句 lazy="false"如下:
<class lazy="false" name="Model.CountryModel, Model" table="Country">
在執行顯示結果上面,完全沒變化,但是用SQL Server Profiler看的到SQL語句如下:
exec sp_executesql N'SELECT personmode0_.PersonId as PersonId0_1_, personmode0_.PersonName as PersonName0_1_, personmode0_.CountryId as CountryId0_1_, countrymod1_.CountryId as CountryId1_0_, countrymod1_.CountryName as CountryN2_1_0_ FROM Person personmode0_ inner join Country countrymod1_ on personmode0_.CountryId=countrymod1_.CountryId WHERE personmode0_.PersonId=@p0',N'@p0 int',@p0=1 --SQL語句等價於: SELECT p.PersonId,p.PersonName,p.CountryId,c.CountryId,c.CountryName FROM Person AS p INNER JOIN Country AS c ON P.CountryId = c.CountryId
可以看到,如果禁止Country表使用延遲加載,那么NHibernate就會被逼得使用Inner Join一次把所有的數據都讀出來,無論你用沒用到另外一張表中的數據。
不可變類,mutable="false"
還是利用這個例子,來看看不可變類是什么意思,我們將Person.hbm.xml的第一行加上一個mutable="false",變為:
<class name="Model.PersonModel, Model" table="Person" mutable="false">
PersonDao.cs寫一個Delete方法如下:
public void Delete() { ISession NSession = NHibernateHelper.GetSession(); PersonModel p = NSession.Get<PersonModel>(1); Session.Delete(p); }
然后在Program.cs中調用它。
PersonDao dao = new PersonDao(); dao.Delete();
你希望看到什么?答案是:SQL Server Profiler顯示沒有任何SQL語句被執行。而再次查詢也同樣還有PersonId為1的Person數據在。
對於NHibernate的映射配置屬性,非常多,不可能一一示例。如果需要查詢比較詳細的映射配置信息,可以到這里http://www.cnblogs.com/kissdodog/archive/2013/02/21/2919886.html。
三、多對多
還是以一個最簡單的示例來說明,一個程序員可以開發多個軟件,一個軟件可以由多個程序員共同開發。典型的數據表如下:
先來看Person.hbm.xml的映射文件配置如下:
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"> <class name="Model.PersonModel, Model" table="Person"> <id name="PersonId" column="PersonId" type="Int32"> <generator class="native"/> </id> <property name="PersonName" column="PersonName" type="String"/> <!-- 多對多關系 對應多個軟件 --> <bag name="Softs" generic="true" table="PersonSoft"> <key column="PersonId" foreign-key="FK_PersonSoft_Person"/> <many-to-many column="SoftId" class ="Model.SoftModel,Model" foreign-key="FK_PersonSoft_Soft"/> </bag> </class> </hibernate-mapping>
Soft.hbm.xml如下:
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"> <class name="Model.SoftModel, Model" table="Soft"> <id name="SoftId" column="SoftId" type="Int32"> <generator class="native"/> </id> <property name="SoftName" column="SoftName" type="String"/> <!-- 多對多關系 對應多個程序員 name屬性名,table中間表名 --> <bag name="Persons" generic="true" table="PersonSoft"> <key column="SoftId" foreign-key="FK_PersonSoft_Soft"/> <!--主鍵列,主鍵表的外鍵名稱--> <many-to-many column="PersonId" class ="Model.PersonModel,Model" foreign-key="FK_PersonSoft_Person"/> <!--外鍵列,外鍵類,外鍵名稱--> </bag> </class> </hibernate-mapping>
SoftModel.cs:
public class SoftModel { public virtual int SoftId { get; set; } public virtual string SoftName { get; set; } //多對多關系:一個軟件由多個程序員開發 public virtual IList<PersonModel> Persons { get; set; } }
PersonModel.cs:
public class PersonModel { public virtual int PersonId{ get; set; } public virtual string PersonName{ get; set; } //多對多關系:一個程序員可以開發多個軟件 public virtual IList<SoftModel> Softs { get; set; } }
現在我們來看一個基本的需求,我們現在知道一個人的Id,要求出這個人所開發的軟件
public class PersonDao { public PersonModel GetPerson(int Id) { ISession NSession = NHibernateHelper.GetSession(); return NSession.Get<PersonModel>(Id); } }
上面代碼實現的是根據Id,查詢到人的實體對象。
Program.cs:
static void Main(string[] args) { PersonDao dao = new PersonDao(); PersonModel p = dao.GetPerson(1); Console.WriteLine(p.PersonName + "開發的軟件有:"); foreach (SoftModel soft in p.Softs) //什么都沒有干,純粹是.出來的 { Console.WriteLine("--" + soft.SoftName); } }
但是到調用的時候,只要我們得到了PersonModel的對象,就能夠直接點出它所開發出的軟件列表。
以上代碼顯示結果如下:
你現在領略到NHibernate的恐怖之處的吧,也知道為什么配置那么復雜了吧,配置復雜了,寫SQL語句的時間都省了。
第一步根據Id查出PersonModel實體類的對象,這個就忽略了。關鍵是第二步,當我們點(.)出Softs的時候,NHibernate做了什么呢?
exec sp_executesql N'SELECT softs0_.PersonId as PersonId1_, softs0_.SoftId as SoftId1_, softmodel1_.SoftId as SoftId3_0_, softmodel1_.SoftName as SoftName3_0_ FROM PersonSoft softs0_ left outer join Soft softmodel1_ on softs0_.SoftId=softmodel1_.SoftId WHERE softs0_.PersonId=@p0',N'@p0 int',@p0=1 --相當於下面的SQL語句 SELECT ps.PersonId,ps.SoftId,s.SoftId,S.SoftName FROM PersonSoft ps LEFT OUTER JOIN Soft s ON ps.SoftId = s.SoftId WHERE ps.PersonId = 1
NHibernate在我們點的時候,生成了SQL語句,並返回了結果。
以前要寫一大坨SQL語句,現在只需要一個點(.),不過千萬要記住,SQL語句不能忘。