Tuesday, 22 January 2008

With Genome, you can map standard 1:n and n:m collections for foreign-key/association table database patterns out of the box by using Collection<T> and <OneToManyCollection/> or <ManyToManyCollection/>.

Compared to arbitrary relationships, which can also be mapped with Genome by using Set<T> and a query, Collection<T> offers the following additional functionality:

  • Elements can be explicitly added and removed from the collection.
  • The collection is fully loaded into memory and kept consistent with in-memory object graph modifications.
  • For n:m collections, Genome can fully hide the association class (mapping the database association table) from the domain model if required.

However, for n:m collections, where the association class is annotated with additional values (besides the foreign keys), the standard Collection<T> mapping does not fit.

To provide better support for those mapping scenarios, I have created a Dictionary-like implementation for annotated many-to-many associations, where we can build the functionality on the existing collection support.

Example

I will use a simple domain model to present the idea. Let’s say we have Departments and Employees in our domain. An employee can work in multiple departments, and a department can have more than one employee. This classic many-to-many association is annotated with a job description. The job description is encapsulated in a struct called Job.

So the logical view looks like this:

In the database, we represent this kind of association with an association class/table as follows:

The task is to implement the Department.Employees property, which represents the annotated n:m relation in a consistent way.

Representing an annotated n:m relationshop in the domain model

In my opinion the best representation for Department.Employees is an IDictionary<Employee, Job>. It is ideal because the employees must be unique within the collection, and the annotation data can be accessed if you additionally specify an Employee (index into the dictionary with that employee). Note that this representation is only possible if the annotation can be represented with a single typ; however, you can encapsulate the annotations with a struct or class to achieve this at any time. You can use the <EmbeddedStruct/> mapping feature to map this struct on the EmployedAs class.

Mapping the association table as a one-to-many collection

First we have to map the one-to-many collection (Department.EmployedAsCollection):

protected abstract Collection<EmployedAs> EmployedAsCollection { get; }

<Member name="EmployedAsCollection">
  <OneToManyCollection parentReference="Department"/>
</Member>

Wrapping the association table into an annotated association

We will wrap this collection with a dictionary implementation to represent the annotated association. I have created a helper class AnnotatedManyToManyDictionary that carries out all necessary transformations. This strongly typed helper needs 4 generic parameters, as you have to specify the association class (TAssoc=EmployedAs), the class owning the collection (TOwner=Department), the “other side” of the association (that is, the key in the dictionary, TKey=Employee) and the annotation that is the value in the dictionary (TValue=Job). Basically, you have to wrap the collection with this helper:

public IDictionary<Employee, Job> Employees
{
  get 
  { 
    return new AnnotatedManyToManyDictionary<EmployedAs, Department, Employee, Job>
      (this, EmployedAsCollection, EmployedAsDepartmentEmployeeJobAccessor.Instance);
  }
}

Helper strategy implementation for getting and setting the keys and values of an association item

The helper class manages the underlying one-to-many collection and the association items to provide the required behavior. As you probably noticed in the constructor call, it still needs a little bit of help. You have to pass a strategy that “knows” how to get and set the key and value properties of the association item. In the current example, the EmployedAsEmployeeJobAccessor strategy knows how to get and set the Employee and Job properties on an EmployedAs object. Currently you have to write this piece of code to make that work:

private class EmployedAsEmployeeJobAccessor : 
  IAnnotatedManyToManyDictionaryAssociationAccessor<EmployedAs, Employee, Job>
{
  public static readonly EmployedAsEmployeeJobAccessor Instance =
    new EmployedAsEmployeeJobAccessor();

  public Employee GetKey(EmployedAs assoc)
  {
    return assoc.Employee;
  }

  public void SetKey(EmployedAs assoc, Employee key)
  {
    assoc.Employee = key;
  }

  public Job GetValue(EmployedAs assoc)
  {
    return assoc.Job;
  }

  public void SetValue(EmployedAs assoc, Job value)
  {
    assoc.Job = value;
  }
}

Usage

Having done this, you can easily iterate through the employees in a department:

Department dep = GetSomeDepartment();
foreach(Employee e in dep.Employees.Key) { ... }

You can also iterate through the association elements to retrieve the associated employees of a department along with their job:

foreach(KeyValuePair<Employee,Job> in d1.Employees) { ... }

The job of an employee now depends on the associated department. The indexer of the employees collection takes an associated employee and looks up the job annotated to association:

Employee emp = GetSomeEmployee();
Job assignedJob = dep.Employees[emp];

Similarly, the job of an employee can be set for a specific department association:

dep.Employees[emp] = assignedJob;

Finally, when associating an employee to a department, the job annotation has to be specified as well:

dep.Employees.Add(emp, assignedJob);
Removing just requires the key, without the annotation:
dep.Employees.Remove(emp);

Limitations

The first limitation is performance with larger collections.. The current implementation uses a linear search for looking up the employee key in the collection, which can cause a performance hit in larger collections when adding or removing items or getting an item’s annotation (using the indexer). The reason for this is that I didn’t want to replace Genome’s internal representation of 1:n collections with a dictionary implementation.

The second limitation is that you need to manually code the helper strategy for getting and setting the annotation value in the association items.

Based on your feedback, we might implement this as a native mapping feature in an upcoming Genome release, thus resolving both limitations described.

Sample code

Please find the source code for the example described above attached to this article.

AnnotatedManyToManyAssociation.zip

Posted by TZ.

Wednesday, 23 January 2008 00:31:42 (W. Europe Standard Time, UTC+01:00)
This feature looks very cool!
It allows to model your domain in a way I would never have dared to think about, when I knew that I have to map it to a database...
Fans of DDD will love it.

How would it look like if 'Job' would not be a primitive element (mapped to a column-value) but another entity (mapped to its own table, and the 'annotation' would be a foreign-key pointing to this table).

I have not though too much about this scenario, but I think I have come across it a lot of times. The solution was always to introduce a association-entity in your domain-model. Would your solution also provide some advantages in this scenario?

Regards
jonas
Thursday, 24 January 2008 13:16:02 (W. Europe Standard Time, UTC+01:00)
Annotating with only a foreign-key (NearObjectReference) should work.

Embedded struct currently does not support to hold reference fields (NearObjectReference), so a combination of NOR and additional properties is not possible.

However, the presented solution does not depend on that the assocation class (Job) is persistent. This means, Job can be any transient class that holds whatever fields you want. The only thing is that you have to implement the GetValue (compose the transient class from the fields of the association class) and SetValue (update the association class based on the transient Job class).
Chris
Comments are closed.