A student often attends many classes and hopefully every class is attended by more than one student. This type of relationship is called many-to-many and in a relational database, an intermediate table usually represents it, with at least two columns referencing the keys of the participating entities.
NHibernate supports many-to-many relationships and does so without having to expose the intermediate table to the code.
ManyToMany
in the MappingRecipes
project.Student
to the folder:public class Student { public virtual Guid Id { get; protected set; } public virtual string Name { get; set; } }
Student.hbm.xml
(in the same folder) with the following XML:<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="MappingRecipes" namespace="MappingRecipes.ManyToMany"> <class name="Student"> <id name="Id"> <generator class="guid.comb"/> </id> <property name="Name"/> </class> </hibernate-mapping>
Course
to the folder:public class Course
{
public Course()
{
Students=new HashSet<Student>();
}
public virtual Guid Id { get; protected set; }
public virtual string Name { get; set; }
public virtual ISet<Student> Students { get; set; }
}
Course.hbm.xml
with the following XML:<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="MappingRecipes"
namespace="MappingRecipes.ManyToMany">
<class name="Course">
<id name="Id">
<generator class="guid.comb"/>
</id>
<property name="Name"/>
<set name="Students" table="CourseStudent">
<key column="CourseId"/>
<many-to-many class="Student" column="StudentId"/>
</set>
</class>
</hibernate-mapping>
Recipe
to the folder:public class Recipe : HbmMappingRecipe { private Guid _frenchId; protected override void AddInitialData( ISession session) { var anna = new Student { Name = "Anna" }; var george = new Student { Name = "George" }; var english = new Course { Name = "English" }; var french = new Course { Name = "French" }; english.Students.Add(anna); french.Students.Add(anna); french.Students.Add(george); session.Save(anna); session.Save(george); session.Save(english); session.Save(french); _frenchId = french.Id; } public override void RunQueries(ISession session) { var course2 = session.Get<Course>(_frenchId); Console.WriteLine("Course name: " + course2.Name); Console.WriteLine("Student count: " + course2.Students.Count()); } }
ManyToMany
recipe. The query log will be similar to the following screenshot:As can be seen in the recipe, mapping a many-to-many relationship is easier than mapping a one-to-many relationship. NHibernate takes care of insertion and deletion of rows in the intermediate table.
In this recipe, we use an ISet
type collection and a set mapping, as opposed to the list mapping used in the previous recipe. It makes sense to do so, since a Student
can't be enrolled to a specific Course
more than once (no duplicates allowed) and we don't really care about the order of the students in the collection (order is not significant). In addition, we didn't add any courses collection in the Student
class. We certainly could have, but since such bi-directionality adds complexity, we'll dig deeper in the Bidirectional class relationships recipe instead.
In the AddInitialData
method, we add two courses and two students and enroll the first student (Anna) to both courses and the second student (George) to only the French course. We save the students and the courses and, finally, store the newly assigned Id of the French course so that we can use it in the next method.
The saves will trigger four INSERT
instances, to save the courses and the students and another three INSERT
instances, will be triggered, to the intermediate table to store the relationships we added.
In RunQueries
, we fetch the course in French, using the id we stored previously and output its name and the number of enrolled students. This will cause two queries to be executed. One to retrieve the course and one to retrieve the related students.
3.141.192.183