目录
1、引言
2、映射
3、查询
4、维护
5、结论
首页 数据库 mysql教程 用NHibernate处理带属性的多对多关系

用NHibernate处理带属性的多对多关系

Jun 07, 2016 pm 04:04 PM
关系 处理 引言

1、引言 老谭在面试开发人员的时候,为了考察他们的数据库开发能力,常常祭出我的法宝,就是大学数据库教程中讲到的一个模式:学生选课。这个模式是这样的: 在这个模式中,学生(Student)和课程(Course)都是实体,分别有主键Id。考试成绩(Score)是学生和课程

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、查询

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

小明所有课程的成绩;化学这门课所有学生的成绩等

都比较容易处理,因为这只需要在一个实体上加过滤条件。

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

select score
  from Course c, Student s
  join c.Students score
 where c.Name=&#39;化学&#39;
   and index(score) = s.Id
   and s.Name=&#39;小明&#39;
登录后复制

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

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

即便是“简单的查询”,如查询小明所有课程的成绩,也有一个问题要注意。这个问题和懒加载相关。

在这个查询中,我们已经知道需要获取所有课程,因此,希望进行预先加载:

 from Student s
 join fetch s.Courses course
where s.Name=&#39;小明&#39;
登录后复制

得到结果后,如果脱离查询的环境,如释放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所表达的,操作起来更为复杂。但这样的代价,我们乐意承担。

这是因为,我们更看重模型设计,更重视概念完整性。是模型决定具体实现,而不是反过来,根据具体实现来修改模型的设计。

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

WIN10服务主机太占cpu的处理操作过程 WIN10服务主机太占cpu的处理操作过程 Mar 27, 2024 pm 02:41 PM

1、首先我们右击任务栏空白处,选择【任务管理器】选项,或者右击开始徽标,然后再选择【任务管理器】选项。2、在打开的任务管理器界面,我们点击最右端的【服务】选项卡。3、在打开的【服务】选项卡,点击下方的【打开服务】选项。4、在打开的【服务】窗口,右击【InternetConnectionSharing(ICS)】服务,然后选择【属性】选项。5、在打开的属性窗口,将【打开方式】修改为【禁用】,点击【应用】后点击【确定】。6、点击开始徽标,然后点击关机按钮,选择【重启】,完成电脑重启就行了。

学习PHP中如何处理特殊字符转换单引号 学习PHP中如何处理特殊字符转换单引号 Mar 27, 2024 pm 12:39 PM

在PHP开发过程中,处理特殊字符是一个常见的问题,尤其是在字符串处理中经常会遇到特殊字符转义的情况。其中,将特殊字符转换单引号是一个比较常见的需求,因为在PHP中,单引号是一种常用的字符串包裹方式。在本文中,我们将介绍如何在PHP中处理特殊字符转换单引号,并提供具体的代码示例。在PHP中,特殊字符包括但不限于单引号(')、双引号(")、反斜杠()等。在字符串

Python 包管理器的天坑陷阱:如何避免 Python 包管理器的天坑陷阱:如何避免 Apr 01, 2024 am 09:21 AM

python包管理器是一个强大且方便的工具,用于管理和安装Python包。然而,使用时若不谨慎,可能会陷入各种陷阱。本文将介绍这些陷阱以及应对策略,以帮助开发者避免它们。陷阱1:安装冲突问题:当多个包提供具有相同名称但不同版本的函数或类时,可能会发生安装冲突。应对:在安装前检查依赖关系,确保包之间没有冲突。使用pip的--no-deps选项避免自动安装依赖项。陷阱2:旧版本包问题:如果未指定版本,包管理器可能会安装最新版本,即使有更稳定或适合您需求的旧版本。应对:在安装时明确指定所需版本,例如p

Java JSP 安全漏洞:防护您的 Web 应用程序 Java JSP 安全漏洞:防护您的 Web 应用程序 Mar 18, 2024 am 10:04 AM

JavaServerPages(jsP)是一种Java技术,用于创建动态的WEB应用程序。JSP脚本在服务器端执行,并在客户端渲染为html。然而,JSP应用程序容易受到各种安全漏洞的影响,这些漏洞可能导致数据泄露、代码执行或拒绝服务。常见安全漏洞1.跨站点脚本(XSS)XSS漏洞允许攻击者将恶意脚本注入Web应用程序,这些脚本将在受害者访问页面时执行。攻击者可以使用这些脚本窃取敏感信息(如cookie和会话ID)、重定向用户或破坏页面。2.注入漏洞注入漏洞允许攻击者向Web应用程序的数据库查询

Java Git 入门:版本控制的入门指南 Java Git 入门:版本控制的入门指南 Mar 27, 2024 pm 02:21 PM

版本控制系统(VCS)是软件开发中不可或缺的工具,它允许开发人员跟踪和管理代码更改。git是一个流行且功能强大的VCS,广泛应用于Java开发中。本指南将介绍Git的基本概念和操作,为Java开发人员提供版本控制的基础知识。Git的基本概念仓库:代码和版本历史记录存储的位置。分支:代码库中的独立开发线,允许开发人员在不影响主开发线的情况下进行更改。提交:代码库中代码的一次更改。回滚:将代码库恢复到以前的提交。合并:将两个或多个分支中的更改合并到一个分支中。Git入门1.安装Git从官方网站下载并

Python ORM 在人工智能和机器学习中的作用 Python ORM 在人工智能和机器学习中的作用 Mar 18, 2024 am 09:10 AM

python对象关系映射(ORM)是一种技术,它允许Python对象和关系型数据库表之间进行无缝交互。在人工智能(ai)和机器学习(ML)应用中,ORM发挥着至关重要的作用,简化了数据访问和管理,并提高了开发效率。数据存储和管理ORM提供了一个对象导向的接口来访问和操作数据库。在AI和ML项目中,通常需要处理大量的数据,包括训练数据集、模型参数和预测结果。ORM允许开发人员以简单易懂的方式与这些数据交互,而无需担心底层的sql语法。这大大减少了开发时间和错误的可能性。例如,在使用Tensorfl

Java 并发集合的未来展望:探索新特性和发展趋势 Java 并发集合的未来展望:探索新特性和发展趋势 Apr 03, 2024 am 09:20 AM

随着分布式系统和多核处理器的兴起,并发集合在现代软件开发中变得至关重要。java并发集合提供了高效且线程安全的集合实现,同时管理并发访问的复杂性。本文探讨了Java并发集合的未来展望,重点关注新特性和发展趋势。新特性JSR354:弹性并发集合jsR354定义了具有弹性行为的新并发集合接口,即使在极端并发条件下也能确保性能和可靠性。这些接口提供了原子性的附加功能,例如支持可变不变式和非阻塞迭代。RxJava3.0:反应式并发集合RxJava3.0引入了反应式编程概念,使并发集合能够与反应式数据流轻

Java 文件操作的哲学:理解文件的本质 Java 文件操作的哲学:理解文件的本质 Mar 21, 2024 pm 03:20 PM

文件是计算机系统中信息存储和管理的基本单元,也是Java文件操作的核心关注点。理解文件的本质对于有效地操作和管理文件至关重要。抽象和层次结构文件本质上是一个抽象概念,代表着存储在持久介质(例如磁盘或内存)中的一组数据。文件的逻辑结构通常由操作系统定义,并提供对数据的组织和访问机制。在Java中,文件通过File类表示,它提供了对文件系统的抽象访问。数据持久性文件的关键特性之一是其数据持久性。与内存中的数据不同,文件中的数据即使在应用程序退出后仍然存在。这种持久性使得文件成为长期存储和共享信息的有

See all articles