内容摘要
单向关联映射
双向关联映射
一、单向关联映射
1.1 单向关联映射的描述
让我们回顾一下之前讲的《多对一关联映射》,如图1.1.1所示,其实“一对多”关联映射就是“多对一”关联映射相反的映射。
图1.1.1
至于“一对多”单向关联映射的代码如下:
{
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 ; }
}
映射文件如下:
< 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 单向关联映射的数据插入
单元测试类的代码如下:
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语句:
< 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中,我们能够观察到,如果“一对多”的外键不允许空,就有可能插入不成功。
我们将映射文件稍作修改:
< 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 virtual int ? ID { get ; set ; }
public virtual string Name { get ; set ; }
public virtual Class Class { get ; set ; }
}
< 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 双向关联映射的数据插入
插入数据的代码如下:
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”属性修改为不允许空:
< 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
我修改单元测试代码:
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”实例的先后顺序:
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”的映射文件:
< 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 >
然后修改单元测试的代码:
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 双向关联映射的数据查询
编写单元测试的代码:
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
欢迎转载,但需保留版权。