我确定之前有人问过这个问题,我不知道要搜索什么,所以它可能是重复的。
我有将新实体添加到数据库的代码。这个实体引用了另一个实体( Role
),我通过服务得到它。服务创建 dbContext
的另一个实例,所以我必须在获取它后将角色附加到上下文。问题是,当我尝试附加两个相同的角色时,出现以下异常:
'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.'
我该怎么做?代码如下:
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();
}
最佳答案
我会与 Attach 分享针对这种情况和类似情况的潜在方法 - 规则非常简单,您永远不应该将具有相同 Id 的 Entity 附加两次。好的一点是,有一种简单的方法可以检查它是否已附加,如果已附加,您可以使用该实体,因此最好的方法是 总是 附加前检查本地实体 任何 实体。
对于您的情况代替
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,您就不需要为了将 TenantRole 附加到上下文而进行 DB 调用。
鉴于代码工作正常,但是一旦有人有很多这样的地方,它就会变得很重。对此的潜在解决方案是为您的上下文创建扩展方法:
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);
...
为了让它工作,你应该添加接口(interface) ISomeInterfaceThatAllYourDBModelsImplements 与此类似(代替 Guid,您可以使用任何其他您喜欢的类型):
public interface ISomeInterfaceThatAllYourDBModelsImplements
{
public Guid Id { get; set; }
}
并更新 租户角色
public class TenantRole: ISomeInterfaceThatAllYourDBModelsImplements
{
[Key]
public Guid Id { get; set; }
...
我希望这可以帮助某人。
关于c# - ef core 多次附加同一个实体,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54440265/