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 ();
}
}
}