1、引言

老谭在面试开发者的时候,为了考察他们的数据库开发能力,经常祭出我的法宝,就是大学数据库教程中讲到的一个模式:学生选课。这个模式是这种:

在这个模式中,学生(Student)和课程(Course)都是实体。分别有主键Id。考试成绩(Score)是学生和课程之间的多对多关系。

基于这个模式,对于新手,能够出一些简单查询要求,对于熟手,能够出一些复杂的查询要求,用起来得心应手。

但今天要说的重点是。怎么用NHibernate实现这个模式。和一般多对多关系稍有不同的是,这个关系带有一个属性,就是考试成绩(Score)。

假设多对多关系上带有属性,我们通常会把这个关系扩充为实体。也就是把一个多对多关系转换为一个实体加上两个多对一关系。

假设多对多关系上有多个属性,将其转换为一个实体还是有必要的,但仅仅有非常少的属性(本例中仅仅有一个),转换为实体实在有些浪费。由于普通情况下,对于实体,我们要为其创建一个人工主键,有了人工主键,又要创建一个序列。在映射时,这个实体自然要有相应的类。这一大堆事情,想想就非常麻烦。

问题的关键是。在概念上这本来就是一个关系,为了实现的方便,而将其转换为一个实体。这个凭空多出来的实体。使概念变得复杂。很别扭。

因此,这里要探究一下,让关系恢复为为关系,怎么用NHibernate来处理。

2、映射

假设关系表Score中没有Score那个属性字段,Student实体能够映射为这种类:

public class Student
{
public virtual long Id { get; set; } public virtual string Name { get; set; } public virtual IList<Course> Courses { get; set; }
}

Course也类似。

但有了属性Score,再这样映射就把这个属性丢了。为了带上Score属性,两个实体应该这样映射:

public class Student
{
public virtual long Id { get; set; } public virtual string Name { get; set; } public virtual IDictionary<Course, int> Courses { get; set; }
}
public class Course
{
public virtual long Id { get; set; } public virtual string Name { get; set; } public virtual IDictionary<Student, int> Students { get; set; }
}

原来的列表(List)变成了字典(Dictionary),字典的主键是原来列表中的元素,值则是关系上的属性值,即考试成绩。

相应的映射文件自然也要调整,结果例如以下:

...
<class name="Course" table="Course"> <id name="Id" unsaved-value="0">
<column name="Id" />
...
</id> <property name="Name">
<column name="Name"/>
</property> <map name="Students" table="Score" lazy="true">
<key column="CourseId"/>
<index-many-to-many column="StudentId" class="Student" />
<element column="Score" />
</map>
</class>

...
<class name="Student" table="Student"> <id name="Id" unsaved-value="0">
<column name="Id" />
...
</id> <property name="Name">
<column name="Name"/>
</property> <map name="Courses" table="Score" lazy="true">
<key column="StudentId"/>
<index-many-to-many column="CourseId" class="Course"/>
<element column="Score" />
</map>
</class>

经过这种映射。多对多关系中的属性带到了类中,并且避免了为关系创建实体——没有Score这种类。对这样映射结果的操作,和常规多对多关系映射方式多生成的基本类似。但也有几个要注意的问题。

3、查询

对于一些简单的查询,如:

  • 小明全部课程的成绩;
  • 化学这门课全部学生的成绩等

都比較easy处理,由于这仅仅须要在一个实体上加过滤条件。

假设须要在两个实体上加过滤条件,如查询小明化学课的成绩,就有些复杂。其查询语句Hql是这种:

select score
from Course c, Student s
join c.Students score
where c.Name='化学'
and index(score) = s.Id
and s.Name='小明'

这里出现了我们非常少用到的hql函数:index()。这是专门应对map的,其目的是获得map的索引字段。

吊诡的是。尽管我们指明,map的key是对象。如Course.Students的key是Student的对象,但index()的结果,仍然是 <index-many-to-many column="..." class="..."/> 中 column字段的值。在上面的查询中。index(score),我们期望的结果是Student对象,但结果却是对象的Id。因此。在查询语句中,我们不得不关联上Student s,并利用 s.Name
进行过滤。

即便是“简单的查询”,如查询小明全部课程的成绩,也有一个问题要注意。

这个问题和懒载入相关。

在这个查询中。我们已经知道须要获取全部课程。因此,希望进行预先载入:

 from Student s
join fetch s.Courses course
where s.Name='小明'

得到结果后,假设脱离查询的环境,如释放Session,在訪问课程时,如:

s.Courses.Select(c => c.Key.Name)

仍会抛出异常。由于,上述的查询并没有把Course对象载入进来。

眼下还不知道怎么预载入map中索引对象。

须要的话,仅仅有依赖懒载入机制。

4、维护

除了查询,在对关系进行维护时,也有一点值得特别注意:save-update类型的cascade无效。

cascade属性,为我们进行关系维护带来不少便利。

在常规(不带属性)的多对多关系中。我们的维护操作能够是这种:

小明选化学课:

using (var tx = session.BeginTransaction())
{
var student = GetStudent(studentName) ?? new Student { Name = studentName, Courses = new List<Course>() }; var course = GetCourse(courseName) ??
new Course { Name = courseName, Students = new List<Student>() }; if (!course.Students.Contains(student))
{
course.Students.Add(student);
} session.SaveOrUpdate(course); tx.Commit();
}

当中,GetStudent(studentName) 和 GetCourse(courseName) 各自是指,依据学生名字或课程名字从数据库中载入对应对象。

在上面的代码片段中,假设数据库中没有,则新建。

维护关系后,进行保存。

须要注意的是,在保存 course 的时候,并没有确保 student 是一个持久化的对象。假设student尚未被持久化,则在保存时,NHibernate会自己主动保存。并维护和course的关系。可以这么做。依赖于Course中关系上的cascade属性定义(第三行末尾):

  <class name="Course" table="Course">
...
<bag name="Students" table="Scoure" lazy="true" cascade="save-update">
<key column="CourseId"/>
<many-to-many column="StudentId" class="Student" />
</bag>
</class>

但在包括属性的多对多关系上。因为要使用map。就无法进行这种配置——配置了也不生效。

假设数据库中尚未保存该学生。我们不得不首先创建并将其持久化(第7行):

using (var tx = session.BeginTransaction())
{
var student = GetStudent(studentName);
if (student == null)
{
student = new Student {Name = studentName, Courses = new Dictionary<Course, int>()};
session.Save(student);
} var course = GetCourse(courseName) ??
new Course {Name = courseName, Students = new Dictionary<Student, int>()}; if (course.Students.ContainsKey(student))
{
course.Students[student] = score;
}
else
{
course.Students.Add(student, score);
} session.SaveOrUpdate(course); tx.Commit();
}

否则,就等着NHibernate抛出异常吧。

5、结论

用Map/Dicitionary表达的多对多关系,要比用Bag/List所表达的,操作起来更为复杂。

但这种代价。我们乐意承担。

这是由于。我们更看重模型设计,更重视概念完整性。是模型决定详细实现,而不是反过来。依据详细实现来改动模型的设计。



用NHibernate处理带属性的多对多关系的更多相关文章

  1. EF Core反向导航属性解决多对一关系

    多对一是一种很常见的关系,例如:一个班级有一个学生集合属性,同时,班级有班长.语文课代表.数学课代表等单个学生属性,如果定义2个实体类,班级SchoolClass和学生Student,那么,班级Sch ...

  2. NHibernate初学六之关联多对多关系

    1:创建三张表T_Course.T_Student.T_Middle:其中一个学生可以对应多个课程,一个课程也可以对应多个学生,用T_Middle存放它们的关系内容: CREATE TABLE [db ...

  3. Java基础-SSM之mybatis一对多和多对一关系映射

    Java基础-SSM之mybatis一对多和多对一关系映射 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.准备测试环境(创建数据库表)  1>.创建customers表: ...

  4. [NHibernate]多对多关系(关联查询)

    目录 写在前面 文档与系列文章 多对多关系关联查询 总结 写在前面 上篇文章介绍了nhibernate中对一对多关系进行关联查询的几种方式,以及在使用过程需要注意的问题.这篇文章对多对多关系的查询处理 ...

  5. NHibernate系列文章十九:NHibernate关系之多对多关系(附程序下载)

    摘要 NHibernate的多对多关系映射由many-to-many定义. 从这里下载本文的代码NHibernate Demo 1.修改数据库 添加Product表 添加ProductOrder表 数 ...

  6. [转]NHibernate之旅(11):探索多对多关系及其关联查询

    本节内容 多对多关系引入 多对多映射关系 多对多关联查询 1.原生SQL关联查询 2.HQL关联查询 3.Criteria API关联查询 结语 多对多关系引入 让我们再次回顾在第二篇中建立的数据模型 ...

  7. (4/18)重学Standford_iOS7开发_框架和带属性字符串_课程笔记

    第四课(干货课): (最近要复习考试,有点略跟不上节奏,这节课的内容还是比较重要的,仔细理解掌握对今后的编程会有很大影响) 本节课主要涉及到Foundation和UIKit框架,基本都是概念与API知 ...

  8. (五)带属性值的ng-app指令,实现自己定义模块的自己主动载入

    如今我们看下怎样使用带属性值的ng-app命令,让ng-app自己主动载入我们自己定义的模块作为根模块. <!DOCTYPE html> <html> <head> ...

  9. IOS开发-UI学习-NSMutableAttributedString(带属性的字符串)的使用

    带属性的字符串: NSString *aa = @"hellochinaIloveYou!"; NSMutableAttributedString *mas = [[NSMutab ...

随机推荐

  1. appium+python自动化-adb offline(5037端口被占)

    前言 adb连手机的时候经常会出现offline的情况,一般杀掉adb,然后重启adb可以解决. 如果发现不管怎么重启adb都连不上,一直出现offlie的情况,这个时候很大可能就是adb的5037端 ...

  2. Result实现类

    package org.apache.struts2.dispatcher; import com.opensymphony.xwork2.ActionInvocation; import com.o ...

  3. 【Luogu】P1122最大子树和(DFS,树上DP)

    题目链接 感觉自己DP好烂啊   道道看题解 钦定1为根,DFS搜索子树权值.如果子树权值大于0就将当前节点加上子树权值,反之就把子树扔掉.最后在所有节点的权值中寻找最优解. void dfs(int ...

  4. 洛谷P2498 [SDOI2012]拯救小云公主 【二分 + 并查集】

    题目 英雄又即将踏上拯救公主的道路-- 这次的拯救目标是--爱和正义的小云公主. 英雄来到boss的洞穴门口,他一下子就懵了,因为面前不只是一只boss,而是上千只boss.当英雄意识到自己还是等级1 ...

  5. BZOJ2244 [SDOI2011]拦截导弹 【cdq分治 + 树状数组】

    题目 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度.并且能够拦截任意速度的导弹,但是以后每一发炮弹都不能高于前一发的高度,其 ...

  6. 洛谷P3758 - [TJOI2017]可乐

    Portal Description 给出一张\(n(n\leq30)\)个点\(m(m\leq100)\)条边的无向图.初始时有一个可乐机器人在点\(1\),这个机器人每秒会做出以下三种行为之一:原 ...

  7. PHP文件锁定机制

    <?php //如果多用户访问一个文件,采用文件锁定机制 /* flock()文件锁定 */ header("Content-Type:text/html;charset=utf8&q ...

  8. 【BZOJ3611】大工程(虚树,DFS序,树形DP)

    题意:有一棵树,树有边权,有若干次询问,给出一些点,求: 1.这些点互相之间的距离之和 2.点对距离中的最大和最小值 n<=1000000 q<=50000并且保证所有k之和<=2* ...

  9. android 服务

    1.创建服务 Exported:是否允许除了当前程序之外的其他程序访问这个服务 Enable:是否启用这个服务 点击完成后自动生成 import android.app.Service; import ...

  10. android应用开发之View的大小计量单位(px、dpi、dp、dip、sp)

    http://blog.csdn.net/ljianhui/article/details/43601495?ref=myread 一.像素(px)与屏幕分辨率 1)px(Pixels ,像素):对应 ...