tags:

views:

278

answers:

7

I am designing an OO object model with a plan to use NHibernate as the data access layer.

I'd like to know the best OO design when dealing with two entities that have a many to many relationship with each other (especially for easy integration with NHibernate).

The objects: User - a single User can be related to multiple Subjects Subject - a single Subject can be related to multiple Users

In SQL, this relationship is straight forward using a many to many table;

tblUser
  userID

tblSubject
  subjectID

tblUserSubject
  userSubjectID
  userID
  subjectID

So, how should the pure objects be created? Should each object contain a collection of the other? Example:

class User 
{
   public int userID {get; set;}
   public List<Subject> subjects {get; set;}
}

class Subject
{
   public int subjectID {get; set;}
   public List<User> users {get; set;}
}

Is there a better way to model this so that NHibernate can easily persist the relationships?

A: 

That should do the trick, but depending on whether you actually need to know which Users are available on a Subject or Subjects for a User one propery can perhaps be left out.

veggerby
+1  A: 

I don't have an answer as far as what design will best jive with NHibernate, but I wanted to comment because this reminds me a lot of Rob Conery's discussion on Hanselminutes about Domain Driven Design.

My visceral reaction is that something isn't right if your pure User contains Subjects at the same time your Subjects contain Users. I'd want to narrow it down to one or the other. Interestingly, Conery was discussing his storefront app and the debate over whether a product has categories or does a category have products. It turned out that neither was the case - it was actually a tangential relationship that would best be handled by a service in the application. (At least, this was the best DDD way of implementing it because it's how his customer related the entities.)

So, DDD aside, I wonder if it would help you take a good hard look at what the true pure relationship is between Users and Subjects, and whether one even contains the other at all. Since I kinda don't like the idea of each of them containing the other, I would probably consider which child collection is used more often. When retrieving a User, do you often use its Subjects? Conversely, while a Subject may have users related to it, do you often use this collection when operating on a Subject object? If not, maybe the collection doesn't directly belong to it.

Again, I can't attest to what design would work best with NHibernate, but I think this is something worth considering.

Kurt Schindler
A: 

You've got the basic structure in place to support many-to-many. Don't forget to add methods to wire up the associations correctly:

class User 
{
   public int userID {get; set;}
   public List<Subject> subjects {get; set;}

   public void AddSubject(Subject subject)
   {
       subject.Users.Add(this);
       this.Subjects.Add(subject);
   }
}

class Subject
{
   public int subjectID {get; set;}
   public List<User> users {get; set;}

   public void AddUser(User user)
   {
       user.Subjects.Add(this);
       this.Users.Add(user);
   }
}
Vijay Patel
A: 

I found the following in NHibernate in Action by Kuate, et al., today. (Page 60, and 61; I changed the object names from the book to keep my original example intact.)

This is similar to Vijay's answer, but it looks like NHiberate likes to see an Interface instead of a list type for the collections.

class User 
{
   public int userID {get; set;}
   private ISet subjects = new HashedSet();

   public ISet subjects {
      get { return subjects; }
      set { subjects = value; }
   }

   public void AddSubject(Subject subject)
   {
       subject.Users.Add(this);
       this.Subjects.Add(subject);
   }
}

class Subject
{
   public int subjectID {get; set;}
   private ISet users = new HashedSet();

   public ISet users {
      get { return users; }
      set { users = value; }
   }

   public void AddUser(User user)
   {
       user.Subjects.Add(this);
       this.Users.Add(user);
   }
}
Pete Lunenfeld
+1  A: 

Found an article which you may find helpful. http://codebetter.com/blogs/peter.van.ooijen/archive/2008/05/29/nhibernate-many-to-many-collections-or-mapping-is-not-one-table-one-class.aspx

To summarize your solution looks very similar. For completeness here is what the nHibernate mapping file might look like for your User object. Using the nHibernate mapping collection for your lists of Users and Subjects.

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="YourAssemblyName" namespace="YourNamespace">
  <class name ="User" table="Users" proxy="User">
    <id name="Id" type="Int32" column="idUser">
      <generator class="identity"></generator>
    </id>                
    <many-to-one name="CreatedBy" class="User" column="idUser"></many-to-one>
    <bag name="subjects" table="tblUserSubject" lazy="false" >
      <key column="idUser"></key>
      <many-to-many class="Subject" column="idSubject"></many-to-many>
    </bag>
  </class>
</hibernate-mapping>

subjects is a bag which uses the table tblUserSubject. The key to this linking table is idUser (assuming that is what your identity column name is). The many to many class is the Subject class we just mapped.

+1  A: 

Avoid many-to-many to begin with. Why and how take a look at http://www.udidahan.com/2009/01/24/ddd-many-to-many-object-relational-mapping/

epitka
+1  A: 

If a User has a reference to all of their Subjects, and a Subject to all of their Users, then when you read a single user you:

  • read that user's details;
  • read that user's subjects;
  • read each of those subjects' users;
  • read each of those subjects' users' subjects;
  • read each of those subjects' users' subjects' users;
  • [...]

Unless you're very careful about lazy loading, you can end up loading a large chunk of both tables just to see a single user record.

I've not used NHibernate before, but in Java Hibernate you can get round this by:

  • using lazy loading, or
  • replacing the many-to-many with:
    • two new classes, UserSummary and SubjectSummary, bound to the same tables but without the many-to-one relationships, and
    • changing User to refer to a bunch of SubjectSummarys and Subject to refer to a bunch of UserSummarys.
Dan Vinton