Mapping a many-to-many relationship

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.

Getting ready

Complete the Getting ready instructions at the beginning of this chapter.

How to do it…

  1. Create a new folder named ManyToMany in the MappingRecipes project.
  2. Add a new class Student to the folder:
    public class Student
    {
        public virtual Guid Id { get; protected set; }
        public virtual string Name { get; set; }
    }
  3. Create an embedded resource mapping named 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>
  4. Add a new class 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; }
    }
  5. Create an embedded resource mapping named 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>
  6. Add a new class named 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());
        }
    }
  7. Run the application and start the ManyToMany recipe. The query log will be similar to the following screenshot:
    How to do it…

Note

The Recipe class in this example derives from the HbmMappingRecipe class. For convenience, this class automatically adds all embedded resources that are located in the same folder as the recipe class.

How it works…

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.

There's more…

For many-to-many relationships, the type of collection used becomes even more important than for one-to-many. We suggest you review the Collections section in the previous recipe for some meaty ingredients.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.12.50