ef核心多次附加同一实体

时间:2019-01-30 12:06:42

标签: c# entity-framework-core

我确定这是之前被问过的,我不知道要搜索什么,所以它很可能是重复的。 我有将新实体添加到数据库的代码。该实体引用了另一个实体(Role),我可以通过服务获得它。服务会创建dbContext的另一个实例,因此在获取上下文后,我必须将角色附加到上下文。问题是,当我尝试附加两个相同的角色时,出现此异常:

  

'Role'无法被跟踪,因为已经为{'Id'}设置了相同键值的另一个实例。附加现有实体时,请确保仅附加一个具有给定键值的实体实例。考虑使用'DbContextOptionsBuilder.EnableSensitiveDataLogging'查看冲突的键值。'

我应该怎么做?下面的代码:

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 个答案:

答案 0 :(得分:0)

我将通过Attach(共享)为这种情况和类似情况共享潜在的方法-规则非常简单,永远不要将具有相同ID的Entity附加两次。很好的一点是,有一种简单的方法可以检查它是否已附加以及是否已附加,您可以只使用该实体,因此最好的方法是在附加任何之前先始终检查本地实体。 strong>实体。 您的情况代替

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

可能是:

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;

因为您知道RoleId,所以无需进行DB调用即可仅将TenantRole附加到上下文。

给出的代码可以正常工作,但是一旦有人拥有很多这样的地方,它就会变得繁重。可能的解决方案是为您的上下文创建扩展方法:

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

因此,您将能够将代码重新编写为:

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

要使其正常工作,您应该添加与此类似的接口 ISomeInterfaceThatAllYourDBModelsImplements (代替Guid,您可以使用任何其他喜欢的类型):

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

并更新 TenantRole

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

我希望这可以对某人有所帮助。