Bradley Uffner
Bradley Uffner

Reputation: 17001

Handling cached entities

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.

What is the proper "Entity Framework Way" of handling this pattern?

Upvotes: 0

Views: 68

Answers (1)

Gert Arnold
Gert Arnold

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

Related Questions