Reputation: 17001
My application uses Code First Entity Framework to manage "Scripts". Scripts can have multiple "Tags" associated to them.
public class Tag
{
public Guid TagId { get; set; }
public string Name { get; set; }
public ICollection<Script> Scripts { get; set; }
}
public class Script
{
public Guid ScriptId { get; set; }
public string Title { get; set; }
...
public ICollection<Tag> Tags { get; set; } = new List<Tag>();
}
When the application starts up it loads a list of all the tags and keeps references to them. When the user edits a script they can add and remove tags on the script. As the user adds tags I simply use script.Tags.Add(tag)
to add the reference. When I save the Script everything works fine the first time, but on subsequent scripts it ends up saving multiple copies of the older scripts and creates duplicate Tags.
I know exactly why it happens. When I call Context.SaveChanges
it fixes up the references and adds the script to the tag's Script collection, which then persist for the next save. What I don't know is, what is the proper way to prevent this from happening?
I've come up with a few solutions that work, but none of them seem ideal, and some of them have serious down-sides. They will also start becoming increasingly complex and error-prone as the application grows and I start having more associations like this. I feel I must be missing something obvious.
.Scripts
property of each Tag
after saving.Tag
in to the context that is used to save the Script
.Context
for the entire life of the application.What is the proper "Entity Framework Way" of handling this pattern?
Upvotes: 0
Views: 68
Reputation: 109252
I think the easiest solution is to draw the junction class into the class model:
public class Tag
{
public Guid TagId { get; set; }
public string Name { get; set; }
}
public class ScriptTag
{
public Guid ScriptId { get; set; }
public Guid TagId { get; set; }
public virtual Script Script { get; set; }
public virtual Tag Tag { get; set; }
}
public class Script
{
public Guid ScriptId { get; set; }
public string Title { get; set; }
...
public virtual ICollection<ScriptTag> ScriptTags { get; set; }
= new List<ScriptTag>();
}
Now you can manipulate the script-tag associations directly without having to load a single Tag
object into the context that does the job. The user can make a selection from the preloaded tags and you only have to create/delete these lightweight ScriptTag
entities accordingly.
Suppose you have a viewModel
object containing selected tags, this is a way to do it:
var script = db.Scripts.Include(s => s.ScriptTags)
.Single(s => s.ScriptId == scriptId);
// Simply replace the `ScriptTags` collection.
script.ScriptTags.Clear();
script.ScriptTags.AddRange(viewModel.Tags.Select(t => new ScriptTag
{
ScriptId = scriptId,
TagId = t.TagId
});
db.SaveChanges();
And this is how to get all tags of a script:
from script in db.Scripts
where script.ScriptId == scriptId
from scriptTag in script.ScripTags
select scriptTag.Tag.Name
Upvotes: 1