Связи между объектами в Fluent NHibernate
Fluent NHibernate позволяет задавать связи один к одному, один ко многим и многие ко многим посредством специальных конструкций: HasOne, HasMany, HasManyToMany.
Рассмотрим примеры создания этих трех типов связей на примерах.
Связь один к одному
Для связи один к одному возьмем объекты «Студент» и «Зачетная книжка» - один студент может иметь только одну зачетную книжку. Для начала создадим классы студента и зачетной книжки:
namespace Fluent.Domain
{
//Домен студента
public class Student
{
public virtual long Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual char Sex { get; set; }
public virtual int Year { get; set; }
//Ссылка на зачетную книжку
public virtual RecordBook RecordBook { get; set; }
}
}
namespace Fluent.Domain
{
//Домен зачетной книжки
public class RecordBook
{
public virtual long Id { get; set; }
public virtual string Number { get; set; }
//Ссылка на студента
public virtual Student Student { get; set; }
}
}
Теперь необходимо создать классы отображения (map-классы):
namespace Fluent.Mappings
{
//Класс отображения зачетной книжки
public class RecordBookMap : ClassMap<RecordBook>
{
public RecordBookMap()
{
//Указание имени таблицы для зачетной книжки
Table("RecordBooks");
//Отображение идентификатора на колонку таблицы
Id(x => x.Id).GeneratedBy.Native();
//Отображение обычного поля на колонку таблицы
Map(x => x.Number);
//Ссылка на студента
References(x => x.Student).Column("StudentId").Cascade.All();
}
}
}
namespace Fluent.Mappings
{
//Класс отображения студента
public class StudentMap : ClassMap<Student>
{
public StudentMap()
{
//Указание имени таблицы для студента
Table("Students");
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.FirstName);
Map(x => x.LastName);
Map(x => x.Sex);
Map(x => x.Year);
//Связь один к одному
HasOne(x => x.RecordBook).ForeignKey("StudentId").Cascade.All();
}
}
}
Как видно из примера, каждый класс отображения содержит конструкцию для указания имени таблицы в базе данных (Table), конструкцию для отображения ключевого поля на таблицу базы данных (Id), конструкции для отображения информационных полей на таблицу базы данных (Map), конструкции для связывания объектов. В данном примере, для того чтобы связать объекты «Студент» и «Зачетная книжка» связью один к одному в классе студента необходима ссылка на объект «Зачетная книжка»:
public virtual RecordBook RecordBook { get; set; }
В классе зачетной книжки для того, чтобы с объекта зачетной книжки можно было получить доступ к студенту, в классе зачетной книжки необходима ссылка на объект «Студент»:
public virtual Student Student { get; set; }
В классах отображения тоже должны быть соответствующие поля. Со стороны студента:
HasOne(x => x.RecordBook).ForeignKey("StudentId").Cascade.All();
Со стороны зачетной книжки должно быть поле:
References(x => x.Student).Column("StudentId").Cascade.All();
В соответствии с классами отображения в базе данных автоматически создаются таблицы, изображенные на рисунке 4.1 и 4.2.
Рисунок 4.1 – Таблица зачетной книжки
Рисунок 4.2 – Таблица студента
Рассмотрим теперь связь один ко многим.
Связь один ко многим
Для связи один ко многим возьмем объекты «Группа» и «Студент». В одной группе может быть много студентов. Один студент может состоять только в одной группе.
Создадим классы домены для группы и студента:
namespace Fluent.Domain
{
//Домен группы
public class Group
{
private IList<Student> studentList = new List<Student>();
public virtual long Id { get; set; }
public virtual string GroupName { get; set; }
public virtual string CuratorName { get; set; }
public virtual string HeadmanName { get; set; }
public virtual IList<Student> StudentList
{
get { return studentList; }
set { studentList = value; }
}
}
}
namespace Fluent.Domain
{
//Домен студента
public class Student
{
public virtual long Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual char Sex { get; set; }
public virtual int Year { get; set; }
public virtual Group Group { get; set; }
}
}
Обратите внимание, что со стороны группы содержится список для хранения студентов.
Создадим классы отображения для группы и студента:
using System;
using System.Collections.Generic;
using FluentNHibernate.Mapping;
using Fluent.Domain;
namespace Fluent.Mappings
{
//Класс отображения группы
public class GroupMap:ClassMap<Group>
{
public GroupMap()
{
Table("Groups");
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.GroupName);
Map(x => x.CuratorName);
Map(x => x.HeadmanName);
//Связь один ко многим
HasMany(x => x.StudentList)
.KeyColumns.Add("GroupId")
Inverse()
Cascade.All();
}
}
}
namespace Fluent.Mappings
{
//Класс отображения группы
public class StudentMap : ClassMap<Student>
{
public StudentMap()
{
Table("Students");
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.FirstName);
Map(x => x.LastName);
Map(x => x.Sex);
Map(x => x.Year);
//Ссылка на руппу
References(x => x.Group).Column("GroupId").Cascade.All();
}
}
}
В отличие от связи один к одному, со стороны один в классе отображения находится конструкция:
HasMany(x => x.StudentList)
.KeyColumns.Add("GroupId")
Inverse()
Cascade.All();
А со стор многие, как и в связи один к одному находится конструкция:
References(x => x.Group).Column("GroupId").Cascade.All();
В соответствии с классами отображения в базе данных автоматически создаются таблицы, изображенные на рисунке 4.1 и 4.2.
Рисунок 4.3 – Таблица группы
Рисунок 4.4 – Таблица студента
Рассмотрим теперь связь многие ко многим
Связь многие ко многим
Для связи многие ко многим выберем объекты «Преподаватель» и «Предмет». Один преподаватель может вести много предметов и один предмет может вести несколько преподавателей.
Создадим классы домены для преподавателя и для предмета:
namespace Fluent.Domain
{
//Класс домена предмета
public class Subject
{
private IList<Teacher> teacherList = new List<Teacher>();
public virtual long Id { get; set; }
public virtual string SubjectName { get; set; }
public virtual int HoursNumber { get; set; }
public virtual IList<Teacher> TeacherList
{
get { return teacherList; }
set { teacherList = value; }
}
}
}
namespace Fluent.Domain
{
//Класс домена преподавателя
public class Teacher
{
private IList<Subject> subjectList = new List<Subject>();
public virtual long Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual IList<Subject> SubjectList
{
get { return subjectList; }
set { subjectList = value; }
}
}
}
Обратите внимание, что класс предмета содержит коллекцию для хранения преподавателей, а класс преподавателей содержит коллекцию для хранения предметов.
Создадим классы отображения для предмета и преподавателя:
namespace Fluent.Mappings
{
//Класс отображения предмета
public class SubjectMap:ClassMap<Subject>
{
public SubjectMap()
{
Table("Subjects");
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.SubjectName);
Map(x => x.HoursNumber);
//Связь многие ко многим
HasManyToMany(x => x.TeacherList)
.Table("TeacherSubject")
.ParentKeyColumn("SubjectId")
.ChildKeyColumn("TeacherId");
}
}
}
namespace Fluent.Mappings
{
//Класс отображения преподавателя
public class TeacherMap:ClassMap<Teacher>
{
public TeacherMap()
{
Table("Teachers");
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.FirstName);
Map(x => x.LastName);
//Связь многие ко многим
HasManyToMany(x => x.SubjectList)
.Table("TeacherSubject")
.ParentKeyColumn("TeacherId")
.ChildKeyColumn("SubjectId");
}
}
}
Обратите внимание, что в классах отображениях с обеих сторон указывается конструкция HasManyToMany
Поскольку связь многие ко многим в реляционных базах данных осуществляется через отдельную таблицу, то в базе данных будет автоматически создано три таблицы, которые представлены на рисунках 4.5, 4.6 и 4.7.
Рисунок 4.5 – Таблица предмета
Рисунок 4.6 – Таблица преподавателя
Рисунок 4.7 – Промежуточная таблица преподавателя и предмета
Более детально со связями между объектами, а также с параметрами связей можно ознакомиться в книгах NHibernate in Action и NHibernate Cookbook.