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