NHibernate从入门到精通系列(9)——一对多关联映射

 内容摘要

    单向关联映射

    双向关联映射

 

  一、单向关联映射

  1.1 单向关联映射的描述

  让我们回顾一下之前讲的《多对一关联映射》,如图1.1.1所示,其实“一对多”关联映射就是“多对一”关联映射相反的映射。

图1.1.1

 

  至于“一对多”单向关联映射的代码如下:

public class Student
    {
        public virtual int? ID { get; set; }

        public virtual string Name { get; set; }
    }

    public class Class
    {
        public virtual int? ID { get; set; }

        public virtual string Name { get; set; }

        public virtual IList<Student> Students { get; set; }
    }

 映射文件如下:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Domain" namespace="Domain">
  <class name="Student" table="T_Student" lazy="true" >
    <id name="ID" type="int" column="StudentID">
      <generator class="native"/>
    </id>
  
    <property name="Name" type="string">
      <column name="Name" length="50"/>
    </property>

  </class>
</hibernate-mapping>


<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Domain" namespace="Domain">
  <class name="Class" table="T_Class" lazy="true" >
    <id name="ID" type="int" column="ClassID">
      <generator class="native"/>
    </id>
  
    <property name="Name" type="string">
      <column name="Name" length="50"/>
    </property>

    <bag name="Students">
      <key column="ClassID"/>
      <one-to-many class="Student"/>
    </bag>
    
  </class>
</hibernate-mapping>

我们看到“Class”类中,有名为“Students” 的属性,其类型是IList<Student>。在映射文件中,我们使用<bag>和<one-to-many>标签来描述“一对多”关联映射。

  

  1.2 单向关联映射的数据插入

   单元测试类的代码如下:


[TestFixture]
    public class OneToManyTest
    {
        private ISessionFactory sessionFactory;

        public OneToManyTest()
        {
            log4net.Config.XmlConfigurator.Configure();
        }

        [SetUp]
        public void Init()
        {
            var cfg = new NHibernate.Cfg.Configuration().Configure("Config/hibernate.cfg.xml");
            sessionFactory = cfg.BuildSessionFactory();
        }

        [Test]
        public void SaveTest()
        {
            using (ISession session = this.sessionFactory.OpenSession())
            {
                var liu = new Student { Name = "刘冬" };
                var zhang = new Student { Name = "张三" };

                var cls = new Class { Name = "1班" };
                cls.Students = new List<Student> { liu, zhang };

                ITransaction tran = session.BeginTransaction();
                try
                {
                    session.Save(liu);
                    session.Save(zhang);

                    session.Save(cls);

                    tran.Commit();
                }
                catch(Exception ex) 
                {
                    tran.Rollback();
                    throw ex;
                }
            }
        }
}

我们配置log4net输出SQL语句:

<?xml version="1.0"?>
<configuration>
  <configSections>

    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
  </configSections>


  <!--log4net配置-->
  <log4net debug="true"> 
    <appender name="EventLogAppender" type="log4net.Appender.EventLogAppender">
      <layout type="log4net.Layout.PatternLayout">
        <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n" />
      </layout>
    </appender> 
    <root>
      <level value="ALL" />
      <appender-ref ref="RollingLogFileAppender" />
    </root>
  </log4net>


  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
</configuration>

运行效果如图1.2.1所示,先成“insert into”语句,然后生成“update”语句修改外键。

图1.2.1

 

  从图1.2.1中,我们能够观察到,如果“一对多”的外键不允许空,就有可能插入不成功。

  我们将映射文件稍作修改:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Domain" namespace="Domain">
  <class name="Class" table="T_Class" lazy="true" >
    <id name="ID" type="int" column="ClassID">
      <generator class="native"/>
    </id>

    <property name="Name" type="string">
      <column name="Name" length="50"/>
    </property>

    <!--设置为不可空-->
    <bag name="Students">
      <key column="ClassID" not-null="true"/>
      <one-to-many class="Student"/>
    </bag>

  </class>
</hibernate-mapping>

运行效果如图1.2.2所示,抛出“ClassID”不允许插入NULL的异常。

图1.2.2

 

  二、双向关联映射

  2.1 双向关联映射的描述

  我们修改一下代码,来实现“一对多”双向关联映射:


public class Student
    {
        public virtual int? ID { get; set; }

        public virtual string Name { get; set; }

        public virtual Class Class { get; set; }
    }

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Domain" namespace="Domain">
  <class name="Student" table="T_Student" lazy="true" >
    <id name="ID" type="int" column="StudentID">
      <generator class="native"/>
    </id>
  
    <property name="Name" type="string">
      <column name="Name" length="50"/>
    </property>

    <many-to-one name="Class" column="ClassID" />
    
  </class>
</hibernate-mapping>

“一对多”双向关联映射指的是:在“一”的这端(“Class”类这端)包含“多”的属性(“Students”);在“多”的这端包含“一”的属性(“Class”属性)。这样两个类构成的循环引用就是双向关联映射。

 

  2.2 双向关联映射的数据插入

  插入数据的代码如下:

[Test]
        public void SaveTest()
        {
            using (ISession session = this.sessionFactory.OpenSession())
            {
                var liu = new Student { Name = "刘冬" };
                var zhang = new Student { Name = "张三" };

                var cls = new Class { Name = "1班" };
                cls.Students = new List<Student> { liu, zhang };

                ITransaction tran = session.BeginTransaction();
                try
                {
                    session.Save(cls);
                    session.Save(liu);
                    session.Save(zhang);

                    tran.Commit();
                }
                catch(Exception ex) 
                {
                    tran.Rollback();
                    throw ex;
                }
            }
        }

 运行效果如图2.2.1所示,运行成功。

图2.2.1

 

  修改“Student”的映射文件,将“Class”属性修改为不允许空:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Domain" namespace="Domain">
  <class name="Student" table="T_Student" lazy="true" >
    <id name="ID" type="int" column="StudentID">
      <generator class="native"/>
    </id>
  
    <property name="Name" type="string">
      <column name="Name" length="50"/>
    </property>

    <!--不允许空-->
    <many-to-one name="Class" column="ClassID" not-null="true"/>
    
  </class>
</hibernate-mapping>

运行效果如图2.2.1所示,抛出“not-null property references a null or transient value”的异常。

图2.2.1

  我修改单元测试代码:


[Test]
        public void SaveTest()
        {
            using (ISession session = this.sessionFactory.OpenSession())
            {
                var liu = new Student { Name = "刘冬" };
                var zhang = new Student { Name = "张三" };

                var cls = new Class { Name = "1班" };
                cls.Students = new List<Student> { liu, zhang };

                liu.Class = cls;
                zhang.Class = cls;

                ITransaction tran = session.BeginTransaction();
                try
                {
                    //先保存班级
                    session.Save(cls);

                    //后保存学生
                    session.Save(liu);
                    session.Save(zhang);

                    tran.Commit();
                }
                catch(Exception ex) 
                {
                    tran.Rollback();
                    throw ex;
                }
            }
        }

 运行效果图2.2.2所示,运行成功。

图2.2.2

 

  我们修改保存“Student”和“Class”实例的先后顺序:


[Test]
        public void SaveTest()
        {
            using (ISession session = this.sessionFactory.OpenSession())
            {
                var liu = new Student { Name = "刘冬" };
                var zhang = new Student { Name = "张三" };

                var cls = new Class { Name = "1班" };
                cls.Students = new List<Student> { liu, zhang };

                liu.Class = cls;
                zhang.Class = cls;

                ITransaction tran = session.BeginTransaction();
                try
                {
                    //先保存学生
                    session.Save(liu);
                    session.Save(zhang);

                    //后保存班级
                    session.Save(cls);

                    tran.Commit();
                }
                catch(Exception ex) 
                {
                    tran.Rollback();
                    throw ex;
                }
            }
        }

运行效果图2.2.3所示,同样抛出“not-null property references a null or transient value”的异常。

图2.2.3

 

  我们修改一下“Class”的映射文件:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Domain" namespace="Domain">
  <class name="Class" table="T_Class" lazy="true" >
    <id name="ID" type="int" column="ClassID">
      <generator class="native"/>
    </id>

    <property name="Name" type="string">
      <column name="Name" length="50"/>
    </property>

    <!--设置为不可空-->
    <bag name="Students" inverse="true" cascade="all">
      <key column="ClassID" not-null="true"/>
      <one-to-many class="Student"/>
    </bag>

  </class>
</hibernate-mapping>

然后修改单元测试的代码:

[Test]
        public void SaveTest()
        {
            using (ISession session = this.sessionFactory.OpenSession())
            {
                var liu = new Student { Name = "刘冬" };
                var zhang = new Student { Name = "张三" };

                var cls = new Class { Name = "1班" };
                cls.Students = new List<Student> { liu, zhang };

                liu.Class = cls;
                zhang.Class = cls;

                ITransaction tran = session.BeginTransaction();
                try
                {
                    //只保存班级
                    session.Save(cls);

                    tran.Commit();
                }
                catch(Exception ex) 
                {
                    tran.Rollback();
                    throw ex;
                }
            }
        }

运行效果如图2.2.4所示,运行成功,并且没有生成“update”语句,只生成“insert into”语句。

图2.2.4

  这样,保存“一”的这端(“Class”实例),就能够将“一”得那端(“Student”)连带保存。其中映射文件中的“inverse”的属性是反转的意思,就是将操作交给双向关联关系中的另一端。

  我们细观察到生成的SQL语句,只生成了“insert into”语句,这样,执行效率就变的高了。

 

  2.3 双向关联映射的数据查询

  编写单元测试的代码:

[Test]
        public void SelectTest()
        {
            using (ISession session = this.sessionFactory.OpenSession())
            {
                var cls = session.Get<Class>(1);

                foreach (var item in cls.Students)
                {
                    Console.WriteLine("学生名为:{0}", item.Name);
                    Console.WriteLine("班级名为:{0}", item.Class.Name);
                }
            }
        }

运行效果如图2.3.1所示,比较以往使用SQL语句编程的代码后,发现调用“一对多”关联映射的集合变得如此方便。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值