Junction objects

Database programmers use junction tables to implement m:n relations. (wikipedia.) Junction objects follow the same logic as junction tables. A junction object contains two object lists, one for the n-side, one for the m-side. Here is a sample for a junction object implementing an engagement, i.e. an m:n-relationship between movies and actors. (One actor is hired for more than one movie; one movie has more than one actor.) The junction object holds the data for a single actor's engagement: a reference to the actor and a reference to the movie. This roughly corresponds to a single record in a junction table. We use bidirectional relationships between both the Actor-Engagement relation and the Movie/Engagement relation. This means that both the Actor and the Movie class must have an object list with Engagements.

// File: Junction.cs

using System;
using System.Linq;
using Remotion.Data.DomainObjects;
using Remotion.Data.DomainObjects.Queries;

namespace Junction.Domain
{
  [DBTable]
  public class Engagement : DomainObject
  {

    [DBBidirectionalRelation("Engagements")]
    public virtual Actor Actor { get; set; }
    [DBBidirectionalRelation("Engagements")]
    public virtual Movie Movie { get; set; }

    static public Engagement NewObject ()
    {
      return DomainObject.NewObject<Engagement> ();
    }

    static public Engagement NewObject (Actor actor)
    {
      var rv = DomainObject.NewObject<Engagement> ();
      rv.Actor = actor;
      return rv;
    }
  }

  [DBTable]
  public class Actor : DomainObject
  {
    public virtual string FirstName { get; set; }
    public virtual string Surname { get; set; }
    [DBBidirectionalRelation("Actor")]
    public virtual ObjectList<Engagement> Engagements { get; set; }
    
    static public Actor NewObject ()
    {
      return DomainObject.NewObject<Actor> ();
    }

    static public Actor[] GetActors ()
    {
      var query = from a in QueryFactory.CreateLinqQuery<Actor> ()
                  select a;
      return query.ToArray ();
    }
  }

  [DBTable]
  public class Movie : DomainObject
  {
    public virtual string Title { get; set; }
    [DBBidirectionalRelation("Movie")]
    public virtual ObjectList<Engagement> Engagements { get; set; }
    static public Movie NewObject ()
    {
      return DomainObject.NewObject<Movie> ();
    }

    static public Movie[] GetMovies ()
    {
      var query = from m in QueryFactory.CreateLinqQuery<Movie> ()
                  select m;
      return query.ToArray ();
    }
  }
}

Now you can use these classes as expected. Most of the interesting work is done by the object lists in the bidirectional relations.

// File: Program.cs


using System;
using Junction.Domain;
using Remotion.Data.DomainObjects;

namespace Junction.Sample
{
  class Program
  {
    static Actor EnterActor (string firstName, string surname)
    {
      var rv = Actor.NewObject ();
      rv.FirstName = firstName;
      rv.Surname = surname;

      return rv;
    }

    static Movie EnterMovie (string title, Actor[] actors)
    {
      var rv = Movie.NewObject();
      rv.Title = title;
      foreach (var actor in actors)
      {
        rv.Engagements.Add (Engagement.NewObject (actor));
      }
      
      return rv;
    }

    static void BuildDomain ()
    {
      using (ClientTransaction.CreateRootTransaction ().EnterDiscardingScope ())
      {
        var arnold = EnterActor ("Arnold", "Schwarzenegger");
        var earl = EnterActor ("Earl", "Boehn");
        var linda = EnterActor ("Linda", "Hamilton");
        var michael = EnterActor ("Michael", "Biehn");
        var edward = EnterActor ("Edward", "Furlong");
        var nick = EnterActor ("Nick", "Stahl");
        var kristanna = EnterActor ("Kristanna", "Loken");

        EnterMovie ("Terminator I", new Actor[] { arnold, earl, linda, michael });
        EnterMovie ("Terminator II", new Actor[] { arnold, earl, linda, edward });
        EnterMovie ("Terminator III", new Actor[] { arnold, earl, nick, kristanna });

        ClientTransaction.Current.Commit ();
      }
    }

    static void DumpDomain ()
    {
      using (ClientTransaction.CreateRootTransaction ().EnterDiscardingScope ())
      {
        foreach (var movie in Movie.GetMovies ())
        {
          Console.WriteLine ("The movie: {0}", movie.Title);
          foreach (var engagement in movie.Engagements)
          {
            Console.WriteLine ("\tStarring: {0} {1}", engagement.Actor.FirstName, engagement.Actor.Surname);
          }
        }

        foreach (var actor in Actor.GetActors ())
        {
          Console.WriteLine ("The actor: {0} {1}", actor.FirstName, actor.Surname);
          Console.WriteLine ("His/her movies:");
          foreach (var engagement in actor.Engagements)
          {
            Console.WriteLine ("\t{0}", engagement.Movie.Title);
          }
        }
      }
    }

    static void Main (string[] args)
    {
      BuildDomain ();
      DumpDomain ();
      Console.ReadLine ();

    }
  }
}