DbContext.Attach 在 Ef Core 3.0 上的行为不同于 2.2,失败并显示 "The instance of entity type cannot be tracked"

DbContext.Attach behaves differently on Ef Core 3.0 than 2.2, fails with "The instance of entity type cannot be tracked"

我正在将使用 EF Core 2.2 的 ASP.NET Core 2.2 应用程序迁移到 ASP.NET Core 3.0 和 EF Core 3.0。

在一种特定方法中,后端从前端接收 DTO,并使用 Automapper 将其转换为等效的实体类型,将其附加到 DbContext,然后保存。 这在 EF Core 2.2 上完美运行,但在 EF Core 3.0 上失败并显示以下消息:

The instance of entity type 'ServiceProvider' cannot be tracked because another
instance with the same key value for {'Id'} is already being tracked

这是有问题的代码:

var sqresponse = _mapper.Map<SurveyQuestionResponse>(sqresponseDTO);

_context.Attach(sqresponse); // <=== Throws exception
_context.SaveChanges();

这里是具有相关 类 的实体:

public class SurveyQuestionResponse
{
    public int? Id { get; set; }
    public List<Answer> Answers { get; set; } = new List<Answer>();
}

public class Answer
{
    public int? Id { get; set; }
    public int SurveyQuestionResponseId { get; set; }
    public ServiceProvider ServiceProvider { get; set; }
}

从前端传递给该方法的数据有两个 Answer,它们都包含一个 ServiceProviderId = 56

{
    answers: [
        {
            // some more properties here
            serviceProviderId: 56,
            serviceProvider: { id: 56, name: "Foo" },
        },
        {
            // some more properties here
            serviceProviderId: 56,
            serviceProvider: { id: 56, name: "Foo" },
        }
    ]
}

这两个 ServiceProvider 实体应该存在于数据库中;在这种情况下,我需要 Answer{Id: 56} 的现有 ServiceProvider 实体相关联 这似乎是 EntityFramework Core 3.0 的问题,但对于 EF Core 2.2 来说完全没问题。

我怀疑这与 this breaking change: DetectChanges honors store-generated key values 有关,但我认为提议的缓解措施可以解决这个特定问题,但在尝试插入新的 ServiceProvider 实体时会创建另一个问题。我说得对吗?

如何使其与 EF Core 3.0 一起使用?

这在 EFCore 2.2 中也不适用,因此您的 usage/test 案例必须完全不同。

提交的 JSON 将被反序列化为您的实体对象。相关的 属性 serviceProvider 将是 deserialized/materialize 每个答案实体对象的实体对象 - 这意味着,在您的示例中,两个答案对象将引用 两个不同的 ServiceProvider 个具有相同 ID 的对象。当它们的引用 answer 对象被附加时,EFCore 也会尝试附加它们引用的 ServiceProvider 对象,但尝试附加第二个答案对象将抛出您收到的错误,因为上下文已经在跟踪 ServiceProvider具有相同ID但不是同一对象的实体。

在尝试将实体添加到上下文之前,您需要检查实体是否已被跟踪,这意味着您不能盲目地将 DTO 附加到上下文。

在通过 DTO 附加 answer 对象之前,首先检查上下文是否已经跟踪了具有相同 ID 的该类型的实体对象,并将该对象分配给 serviceProvider 属性:

var attachedServiceProvider = dbContext.ServiceProviders
    .Local
    .FirstOrDefault(sp => sp.Id = answerObject.serviceProvider.Id);

if( null == attachedServiceProvider )
{
    dbContext.Attach(serviceProvider);
    attachedServiceProvider = serviceProvider;
}

answerObject.serviceProvider = attachedServiceProvider;

我最终通过确保只填写 answer.ServiceProviderId 来解决它,并且 answer.ServiceProvider 在附加时是 null

传给后端的JSON现在看起来是这样的,那么:

{
    answers: [
        {
            // some more properties here
            serviceProviderId: 56,
            serviceProvider: null,
        },
        {
            // some more properties here
            serviceProviderId: 56,
            serviceProvider: null,
        }
    ]
}

有了这个,对 _context.Attach(...)_context.SaveChanges() 的调用就没有问题了。