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

本文介绍了NHibernate中单向和双向关联映射的概念及应用,包括代码示例、映射文件配置、数据插入和查询过程,展示了如何解决外键约束等问题。

  

  内容摘要

    单向关联映射

    双向关联映射

 

  一、单向关联映射

  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语句编程的代码后,发现调用“一对多”关联映射的集合变得如此方便。

图2.3.1

 

  代码下载

  出处:http://www.cnblogs.com/GoodHelper/archive/2011/03/03/nhibernate09.html

  欢迎转载,但需保留版权。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值