Managing 1:N and N:N Object Relations

object relationsWhen you design a domain model you normally have lots of 1:n and n:n relations. Many developers are quite familiar how to translate such relations into a relational model. But how do you translate such relations into an object-model? There’s no hard guideline for that. In this post I explain what I usually do.

The Domain Model

For the examples I use a this small domain-model. Our application has authors, which write posts. An author has multiple posts and a post has only one author. Each post can have multiple tags and a tag is used for multiple posts.

The Possibilities

The majority of object oriented language give two tools for this job, references and collections. And therefore a lot of possibilities.

  1. Referencing from the ‘child’. In our example the post would have a reference to the author.
  2. Referencing from the ‘parent’. In our example the author would have a collection of all its posts.
  3. Bidirectional reference, in our example the post has a reference to the author and the author a collection of his posts.

And in the n:n scenario all this possibilities above also apply. Just that each participant uses collections.

Navigation-Paths Dictate

Which one of the 3 possibilities do you pick? The main criteria is how you’re application navigates through the data. For example when you’re accessing the blog posts and only want to know who has written this post, you add a reference to the author. When your always getting a post via its author, you add a collection of posts to the author. And when your application uses both navigation paths, you use a bidirectional relation.

The same applies for the post-tag relationship. When you only want to know which tag a post has, you add a collection to the post with the tags. When you want to know which post have a certain tag, you add a collection of all posts to the tag.

Bidirectional References: A “chicken or the egg” dilemma

As soon as you have bidirectional references, you cannot avoid a ‘chicken or the egg’-dilemma. You have to create one instance first and then later assign it. Sometimes it’s easy solvable by adding a factory-method to the parent-class. Like this:

public Post NewPost()
{
	var post = new Post(this);
	AddPost(post);
	return post;
}
But often there’s no clear ‘parent’ or natural order. Especially in n:n relations. For this cases I use my special extension-methods. Let’s start with a simple class which represents the author and a post

Then I ‘upgrade’ the properties and methods with extension-methods to make them relation-aware. Basically on each property/method I also state what happens to the relationship-partner. Note that you shouldn’t expose the collections directly. Instead only expose it a read-only IEnumerable and provide additional, domain-specific manipulation-methods. Here’s the code

// upgraded author-property
public Author Author
{
	get { return _author; }
	set { _author = value.AddToParent(this,()=>value.AddPost); }
}

// upgraded AddPost-method on the Author-class

public void AddPost(Post post)
{
	_posts.AddAsChild(post, c => c.Author = this);
}

public bool RemoveChild(Child item)
{
        return children.RemoveAsChild(item, c => c.Parent = null);
}
Now I can assign, add or remove the object freely, and it’s ensured that the relation is in a consistent state

Of course this also works for n:n-relations. For example we start with the relation between tags and posts

public class Post
{
	private readonly ICollection<Tag> _tags = new HashSet<Tag>();

	public void AddTag(Tag item)
	{
		_tags.Add(item);
	}

	public bool RemoveTag(Tag item)
	{
		return _tags.Remove(item);
	}

	public string Text
	{
		get; set;
	}

	public IEnumerable<Tag> Tags
	{
		get { return _tags; }
	}
}

public class Tag
{
	private readonly ICollection<Post> _posts = new HashSet<Post>();

	public void AddPost(Post item)
	{
		_posts.Add(item);
	}

	public bool RemovePost(Post item)
	{
		return _posts.Remove(item);
	}

	public IEnumerable<Post> Posts
	{
		get { return _posts; }
	}
}
Again, we ‘upgrade’ the add and remove method for the relations

And oh wonder, we can freely add and remove and the relation is kept in a consistent state

{
    // when you add a tag, the post will be added to the tag
    var post = new Post();
    var tag = new Tag();
    post.AddTag(tag);

    AssertEqual(post,tag.Posts.First());
}
{
    // when you add a post to a tag, the post will have the tag
    var tag = new Tag();
    var post = new Post();
    tag.AddPost(post);

    AssertEqual(tag, post.Tags.First());

    // and removing also is bidirectional
    tag.RemovePost(post);

    AssertEqual(false, post.Tags.Any());
}

Implementation of the Extension Methods

As you can imagine, the implementation isn’t pretty. Especially to avoid endless recursion, some hacks are required. I don’t want to go into detail here (maybe another time). You can take a look at the source yourself. Here it is:  the extension, the tests, tuple class.

There a certainly cleaner ways to achieve something similar, like special relation-collections. But for my projects this solution is more than enough.

Conclusion

Managing object-relations can be very easy or can be pain. When you have reference in one direction it’s straight forward. But as soon as bidirectional references are required, it can be tricky.

Anyway, critic, tips for improvements or links to good articles on this topic are welcome.

Tagged on: ,

One thought on “Managing 1:N and N:N Object Relations

  1. Pingback: db4o in the Real World - Using db4o on a web project | emphess .NET

Leave a Reply

Your email address will not be published. Required fields are marked *