ef core attach same entity multiple times

c# entity-framework-core

Question

I'm sure this was asked before, I don't know what to search for, so it's probably duplicate. I have code that adds new entity to database. This entity has reference to another entity(Role), and I get it via service. Service creates another instance of dbContext, so I have to attach role to the context after I fetch it. The problem is, when I try to attach two same roles, I get this exception:

'Role' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.'

How should I do it? Code below:

using (var context = new TenantContext(schemaName, connectionString))
{
    ApprovalTemplates templates = new ApprovalTemplates();
    ApprovalTemplate template = new ApprovalTemplate();
    template.Approvers = new List<StageTemplate>();

    foreach (var stage in request.Stages)
    {
        var temp = new StageTemplate();
        temp.Order = stage.Order;
        temp.Name = stage.Name;
        var role = roleService.GetById(stage.RoleId, schemaName);//here I get the role
        temp.AvailableActions = new List<ApprovalActionTemplate>();

        foreach (var actionId in stage.Actions)
            temp.AvailableActions.Add(context.ApprovalActions.First(a => a.Id == actionId));

        //when I try to add already attached role, exception is thrown
        context.TenantRoles.Attach(role);
        temp.Role = role;
        template.Approvers.Add(temp);
    }

    templates.PRApprovalTemplate = template;
    context.ApprovalTemplates.Add(templates);
    context.SaveChanges();
}
1
2
1/30/2019 12:09:21 PM

Popular Answer

I would share potential approach for this and similar cases with Attach - the rule is very simple, you should never attach Entity with the same Id twice. Good point that there is an easy way to check if it's already attached and if it's attached, you can just use that entity, so best way is to always check local entities before attaching any Entity. For your case in place of

var role = roleService.GetById(stage.RoleId, schemaName);//here I get the role

it may be:

var localRole = context.Set<TenantRole>().Local.FirstOrDefault(entry => entry.Id.Equals(stage.RoleId));
if (localRole == null)
{
    localRole = new TenantRole
    {
        Id = stage.RoleId,
    };
    Context.TenantRoles.Attach(localRole);
}
...
temp.Role = localRole;

Because if you know RoleId, you do not need to make DB call just to attach TenantRole to the Context.

Given code works fine, but once someone have many-many places like this, it's becomes to heavy. Potential solution for this would be creating extension method for your Context:

public static class RepositoryExtensions
{
    public static T LocalContextEntitiesFinder<T>(this TenantContext context, Guid id) where T : class, ISomeInterfaceThatAllYourDBModelsImplements, new()
    {
        var localObj = context.Set<T>().Local.FirstOrDefault(entry => entry.Id.Equals(id));
        if (localObj != null)
        {
            return localObj;
        }
        localObj = new T
        {
            Id = id
        };
        context.Set<T>().Attach(localObj);
        return localObj;
    }
}

So you will be able to re-write your code to something like:

...
temp.Role = context.LocalContextEntitiesFinder<TenantRole>(id: stage.RoleId);
...

To make it work, you should add interface ISomeInterfaceThatAllYourDBModelsImplements similar to this (in place of Guid you can use any other type you like):

public interface ISomeInterfaceThatAllYourDBModelsImplements
{
    public Guid Id { get; set; }
}

And update TenantRole

public class TenantRole: ISomeInterfaceThatAllYourDBModelsImplements
{
    [Key]
    public Guid Id { get; set; }
...

I hope this may help somebody.

0
2/18/2020 10:35:23 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow