This is indeed a nuisance.
I've not tried m2m4ria and do it manually on the client, ie. I expose the bridge table in the domain service. Sometimes it turns out to be a good idea anyway if the bridge table is later elevated to carry more data.
To ease the pain of managing the bridge table on the client I've written some helper you might want to consider yourself.
public interface ILinkEntity
where LinkEntity : Entity, ILinkEntity
where SourceEntity : Entity, ILinkedSourceEntity
where TargetEntity : Entity
{
SourceEntity Source { get; set; }
TargetEntity Target { get; set; }
}
public interface ILinkedSourceEntity
where SourceEntity : Entity, ILinkedSourceEntity
where LinkEntity : Entity, ILinkEntity
where TargetEntity : Entity
{
EntityCollection Links { get; }
ObservableCollection Targets { get; set; }
}
public static class ManyToManyHelper
{
public static void UpdateLinks(this ILinkedSourceEntity source, EntitySet set)
where SourceEntity : Entity, ILinkedSourceEntity
where LinkEntity : Entity, ILinkEntity, new()
where TargetEntity : Entity
{
if (!(source is SourceEntity)) throw new Exception("Expected source to be a SourceEntity.");
var toAdd = (
from target in source.Targets
where source.Links.FirstOrDefault(le => le.Target.Equals(target)) == null
select target
).ToArray();
foreach (var target in toAdd) source.Links.Add(new LinkEntity() { Source = source as SourceEntity, Target = target });
var toRemove = (
from link in source.Links
where source.Targets.FirstOrDefault(te => te.Equals(link.Target)) == null
select link
).ToArray();
foreach (var link in toRemove)
{
source.Links.Remove(link);
// This can happen when the entities had not yet been added to the context.
set.Remove(link);
}
}
public static void UpdateTargets(this ILinkedSourceEntity source)
where SourceEntity : Entity, ILinkedSourceEntity
where LinkEntity : Entity, ILinkEntity, new()
where TargetEntity : Entity
{
if (source.Targets == null)
{
source.Targets = new ObservableCollection();
}
else
{
source.Targets.Clear();
}
foreach (var link in source.Links) source.Targets.Add(link.Target);
}
}
I have this in a file called ManyToManyUtils and it should live somewhere where your domain entities can reference them (so typically in the domain client project).
I then augment the respective auto-generated domain entities to support those interfaces, eg. like this:
public partial class Question : ILinkedSourceEntity
{
EntityCollection ILinkedSourceEntity.Links
{
get { return QuestionCategories; }
}
public ObservableCollection Categories { get; set; }
ObservableCollection ILinkedSourceEntity.Targets
{
get { return Categories; }
set { Categories = value; }
}
}
public partial class QuestionCategory : ILinkEntity
{
Question ILinkEntity.Source { get { return Question; } set { Question = value; } }
Category ILinkEntity.Target { get { return Category; } set { Category = value; } }
}
public partial class Category
{
}
So in this example each Question can be in many categories. Category as a domain entity needs not to be modified.
I usually augment domain entity classes with properties frequently anyway, so I often already have those partial classes.
Now I can bind views against those new collection properties. However, I still need to call the helper update methods to sync the bridge table with those helper collection properties.
So after each load or refresh from the domain services you have to call:
myQuestion.UpdateTargets();
And after each edit by the user (eg from a SelectionChanged handler in the view, or - if you are happy with the consequences - just before you call SaveChanges), call:
myQuestion.UpdateLinks(myContext.QuestionCategories);
That way, the nastiness is factored out as much as possible.