在關聯式資料庫設計中,多對多關係是非常常見的模式。例如學生選課系統中,一個學生可以選修多門課程,而一門課程也可以被多位學生選修。這種關係在資料庫中通常透過一張中介表來實現。本文將詳細說明如何在 NHibernate 中配置多對多映射,並展示實際操作範例。
以學生選課系統為例,涉及四張表,核心關係集中在三張表:
外鍵關係如下:
using System;
using System.Collections;
namespace NHibernateSample.Model
{
public class Student
{
protected int _id;
protected string _name;
protected string _phone;
protected Class _class;
protected IList _courses;
public Student() { }
public Student(string name, string phone, Class classes)
{
this._name = name;
this._phone = phone;
this._class = classes;
}
public virtual int Id { get; set; }
public virtual string Name
{
get { return _name; }
set
{
if (value != null && value.Length > 50)
throw new ArgumentOutOfRangeException("Invalid value for Name", value, value.ToString());
_name = value;
}
}
public virtual string Phone
{
get { return _phone; }
set
{
if (value != null && value.Length > 15)
throw new ArgumentOutOfRangeException("Invalid value for Phone", value, value.ToString());
_phone = value;
}
}
public virtual Class Class { get; set; }
public virtual IList Courses
{
get
{
if (_courses == null) _courses = new ArrayList();
return _courses;
}
set { _courses = value; }
}
}
}
using System;
using System.Collections;
namespace NHibernateSample.Model
{
public class Course
{
protected int _id;
protected string _courseName;
protected string _teacher;
protected DateTime _time;
protected string _address;
protected IList _students;
public Course() { }
public Course(string courseName, string teacher, DateTime time, string address)
{
this._courseName = courseName;
this._teacher = teacher;
this._time = time;
this._address = address;
}
public virtual int Id { get; set; }
public virtual string CourseName
{
get { return _courseName; }
set
{
if (value != null && value.Length > 50)
throw new ArgumentOutOfRangeException("Invalid value for CourseName", value, value.ToString());
_courseName = value;
}
}
public virtual string Teacher
{
get { return _teacher; }
set
{
if (value != null && value.Length > 50)
throw new ArgumentOutOfRangeException("Invalid value for Teacher", value, value.ToString());
_teacher = value;
}
}
public virtual DateTime Time { get; set; }
public virtual string Address
{
get { return _address; }
set
{
if (value != null && value.Length > 50)
throw new ArgumentOutOfRangeException("Invalid value for Address", value, value.ToString());
_address = value;
}
}
public virtual IList Students
{
get
{
if (_students == null) _students = new ArrayList();
return _students;
}
set { _students = value; }
}
}
}
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Model" namespace="NHibernateSample.Model">
<class name="NHibernateSample.Model.Student, Model" table="Students">
<id name="Id" type="Int32" unsaved-value="0">
<column name="ID" length="4" sql-type="int" not-null="true" unique="true" index="PK_Students"/>
<generator class="native" />
</id>
<property name="Name" type="String">
<column name="Name" length="50" sql-type="varchar" not-null="false"/>
</property>
<property name="Phone" type="String">
<column name="Phone" length="15" sql-type="varchar" not-null="false"/>
</property>
<many-to-one name="Class" class="NHibernateSample.Model.Class, Model">
<column name="ClassID" length="4" sql-type="int" not-null="false"/>
</many-to-one>
<!-- 多對多關聯配置重點 -->
<bag name="Courses" generic="true" table="SelectCourse">
<key column="StudentID" foreign-key="FK_SelectCourse_Students"/>
<many-to-many column="CourseID" foreign-key="FK_SelectCourse_Course"
class="NHibernateSample.Model.Course, Model" />
</bag>
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Model" namespace="NHibernateSample.Model">
<class name="NHibernateSample.Model.Course, Model" table="Course">
<id name="Id" type="Int32" unsaved-value="0">
<column name="ID" length="4" sql-type="int" not-null="true" unique="true" index="PK_Course"/>
<generator class="native" />
</id>
<property name="CourseName" type="String">
<column name="CourseName" length="50" sql-type="varchar" not-null="false"/>
</property>
<property name="Teacher" type="String">
<column name="Teacher" length="50" sql-type="varchar" not-null="false"/>
</property>
<property name="Time" type="DateTime">
<column name="`Time`" length="8" sql-type="datetime" not-null="false"/>
</property>
<property name="Address" type="String">
<column name="Address" length="50" sql-type="varchar" not-null="false"/>
</property>
<!-- 反向多對多關聯配置 -->
<bag name="Students" generic="true" table="SelectCourse">
<key column="CourseID" foreign-key="FK_SelectCourse_Course"/>
<many-to-many column="StudentID" class="NHibernateSample.Model.Student, Model"/>
</bag>
</class>
</hibernate-mapping>
用於映射集合屬性,對應 .NET 中的 IList 或 IList<T>。屬性說明:
name:實體類別中的集合屬性名稱table:中介表名稱generic:是否使用泛型集合定義中介表中對應「本端」實體的外鍵欄位:
column="StudentID" 對應 SelectCourse 表的 StudentID 欄位column="CourseID" 對應 SelectCourse 表的 CourseID 欄位定義指向「另一端」實體的關聯:
column:中介表中對應另一端實體的外鍵欄位class:另一端實體的完整類型名稱foreign-key:指定外鍵約束名稱(可選)核心概念總結:
// 建立物件
Class cls = new Class();
Student stu = new Student();
Course c = new Course();
// 設定班級資料
cls.ClassCode = "03510236";
cls.StudentsAmount = 36;
// 設定學生資料
stu.Class = cls;
stu.Name = "teau";
stu.Phone = "0897658";
// 設定課程資料
c.Address = "Beijing";
c.CourseName = "C++";
c.Teacher = "Tao";
c.Time = DateTime.Now;
// 建立雙向關聯
stu.Courses.Add(c);
c.Students.Add(stu);
// 持久化操作
ClassesBLL cBLL = new ClassesBLL();
CourseBLL courseBLL = new CourseBLL();
try
{
if (cBLL != null && cls != null)
{
cBLL.AddStudent(cls); // 透過班級級聯儲存學生
courseBLL.AddCourse(c); // 儲存課程及關聯
}
}
catch (Exception ex)
{
// 建議記錄日誌後重新拋出
throw;
}
多對多關係本質上可以視為兩個一對多關係的組合:
如果不想使用 <many-to-many>,可以建立 SelectCourse 實體類別,分別配置兩個一對多關係。這樣做的優點是可以在中介表中儲存額外資訊(如選課時間、成績等),缺點是程式碼較為繁瑣。
NHibernate 中的多對多映射雖然概念上較為繞,但只要掌握 <key> 和 <many-to-many> 的角色定位,理解「中介表如何連接兩端實體」,配置就不再困難。實務上建議:
cascade 與 inverse 避免重複儲存或資料不一致希望本文能幫助大家更清晰地理解 NHibernate 多對多配置!若有錯誤或建議,歡迎指正交流。