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

来源:互联网 发布:柳未来知乎 编辑:程序博客网 时间:2024/05/16 17:30


  映射文件,用于告诉NHibernate数据库里的表、列于.Net程序中的类的关系。因此映射文件的配置非常重要。

一、一对一

  NHibernate一对一关系的配置方式使用<one-to-one>配置节点。

  当我们两个表拥有相同的主键字段,主键值相同的需要关联在一起。比较典型的一个例子是,一个对象的属性太多,常用的和不常用的分开存放。例如一个文章表,我们将文章内容字段,提取出来作为一个单独的字段,因为比较长。

  下面我们来新建两张表如下:

  

  本来,Article表还有很多字段,比如添加时间,所属栏目,是否高亮,是否置顶等等,但是本处仅仅做示范NHibernate一对多一配置之使用。因此简略了。

  Article.hbm.xml

<?xml version="1.0"encoding="utf-8"?>

<hibernate-mappingxmlns="urn:nhibernate-mapping-2.2">

  <classname="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-mappingxmlns="urn:nhibernate-mapping-2.2">

  <classname="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();

        }

    }

  输出结果如下:

  

二、一对多

  来看以下两张表,这是一个典型的一对多关系。人与国家:

  Country表:

  

  Person表:

  
  NHibernate映射文件配置基础,一对多配置示例。先来看看映射文件:

  Country.hbm.xml

<?xml version="1.0"encoding="utf-8"?>

<hibernate-mappingxmlns="urn:nhibernate-mapping-2.2">

  <classname="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-mappingxmlns="urn:nhibernate-mapping-2.2">

  <classname="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();

            returnNSession.QueryOver<CountryModel>().List();

        }

    }

}

    PersonDao.cs:

namespace Dao

{

    public class PersonDao

    {public IList<PersonModel> GetPersonList()

        {

            ISession NSession =NHibernateHelper.GetSession();

            returnNSession.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里加如下一个方法:

       publicPersonModel 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 asCountryId0_0_ FROM Person personmode0_ WHERE

personmode0_.PersonId=@p0',N'@p0int',@p0=1

--第一条:等价于 SELECTPersonId,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_ WHEREcountrymod0_.CountryId=@p0',N'@p0

int',@p0=1

--第二条:等价于 SELECT CountryId,CountryNameFROM WHEE CountryId = 1

  留意到NHibernate并没有采用inner join的语法,将Country的数据也一并从数据库读到程序中,而是当C#5秒后要用到CountryName这个东西的时候,它才去数据库读取。很明显的结论,如果C#不打算输出CountryName,NHibernate根本不会执行第二条SQL语句。

  延迟加载 lazy:true

  下面再来一点点变种,在Country.hbm.xml映射文件里的第一行加上一句 lazy="false"如下:

  <classlazy="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 asCountryId0_1_, countrymod1_.CountryId as

CountryId1_0_,countrymod1_.CountryName as CountryN2_1_0_ FROM Person personmode0_ inner joinCountry countrymod1_ on personmode0_.CountryId=countrymod1_.CountryId WHERE

personmode0_.PersonId=@p0',N'@p0int',@p0=1

--SQL语句等价于:

SELECTp.PersonId,p.PersonName,p.CountryId,c.CountryId,c.CountryNameFROM Person AS p

INNERJOIN Country ASc

ON P.CountryId=c.CountryId

  可以看到,如果禁止Country表使用延迟加载,那么NHibernate就会被逼得使用Inner Join一次把所有的数据都读出来,无论你用没用到另外一张表中的数据。

   不可变类,mutable="false"

   还是利用这个例子,来看看不可变类是什么意思,我们将Person.hbm.xml的第一行加上一个mutable="false",变为:

<classname="Model.PersonModel,Model" table="Person" mutable="false">

  PersonDao.cs写一个Delete方法如下:

publicvoid Delete()

{

    ISession NSession =NHibernateHelper.GetSession();

    PersonModel p =NSession.Get<PersonModel>(1);

   Session.Delete(p);

}

  然后在Program.cs中调用它。

 PersonDao dao = newPersonDao();

 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-mappingxmlns="urn:nhibernate-mapping-2.2">

  <classname="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-mappingxmlns="urn:nhibernate-mapping-2.2">

  <classname="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:

   publicclass 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,要求出这个人所开发的软件

   publicclass PersonDao

    {

        publicPersonModel GetPerson(int Id)

        {

            ISession NSession =NHibernateHelper.GetSession();

            returnNSession.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 asSoftId1_, softmodel1_.SoftId as SoftId3_0_, softmodel1_.SoftName asSoftName3_0_ FROM PersonSoft softs0_

left outer join Softsoftmodel1_ on softs0_.SoftId=softmodel1_.SoftId WHERE softs0_.PersonId=@p0',N'@p0int',@p0=1

--相当于下面的SQL语句

SELECT ps.PersonId,ps.SoftId,s.SoftId,S.SoftNameFROM PersonSoft ps

LEFTOUTER JOIN Soft sONps.SoftId = s.SoftId

WHERE ps.PersonId= 1

  NHibernate在我们点的时候,生成了SQL语句,并返回了结果。

  以前要写一大坨SQL语句,现在只需要一个点(.),不过千万要记住,SQL语句不能忘。

 

0 0