NHibernate 集合映射基礎(第四篇) - 一對一、 一對多、多對多小示例

 映射文件,用於告訴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語句,並返回了結果。

發佈了44 篇原創文章 · 獲贊 73 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章