首页 > 解决方案 > 尝试通过属性默认值更改关系时出现意外的 InvalidOperationException

问题描述

在下面的示例代码中,执行以下操作时出现以下异常db.Entry(a).Collection(x => x.S).IsModified = true

System.InvalidOperationException:“无法跟踪实体类型“B”的实例,因为已在跟踪另一个具有键值“{Id:0}”的实例。附加现有实体时,请确保仅附加一个具有给定键值的实体实例。

为什么不添加而不是附加 B 的实例?

奇怪的是,文档IsModified没有指定InvalidOperationException为可能的例外。无效的文档或错误?

我知道这段代码很奇怪,但我写它只是为了了解 ef core 在一些奇怪的 egde 情况下是如何工作的。我想要的是解释,而不是解决方法。

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    public class A
    {
        public int Id { get; set; }
        public ICollection<B> S { get; set; } = new List<B>() { new B {}, new B {} };
    }

    public class B
    {
        public int Id { get; set; }
    }

    public class Db : DbContext {
        private const string connectionString = @"Server=(localdb)\mssqllocaldb;Database=Apa;Trusted_Connection=True";

        protected override void OnConfiguring(DbContextOptionsBuilder o)
        {
            o.UseSqlServer(connectionString);
            o.EnableSensitiveDataLogging();
        }

        protected override void OnModelCreating(ModelBuilder m)
        {
            m.Entity<A>();
            m.Entity<B>();
        }
    }

    static void Main(string[] args)
    {
        using (var db = new Db()) {
            db.Database.EnsureDeleted();
            db.Database.EnsureCreated();

            db.Add(new A { });
            db.SaveChanges();
        }

        using (var db = new Db()) {
            var a = db.Set<A>().Single();
            db.Entry(a).Collection(x => x.S).IsModified = true;
            db.SaveChanges();
        }
    }
}

标签: c#language-lawyerentity-framework-core-3.1

解决方案


提供的代码中的错误原因如下。

当您A从数据库中创建实体时,它的属性S会使用包含两条新记录的集合进行初始化BId这个新实体中的每一个B都等于0

// This line of code reads entity from the database
// and creates new instance of object A from it.
var a = db.Set<A>().Single();

// When new entity A is created its field S initialized
// by a collection that contains two new instances of entity B.
// Property Id of each of these two B entities is equal to 0.
public ICollection<B> S { get; set; } = new List<B>() { new B {}, new B {} };

执行代码行后,实体var a = db.Set<A>().Single()集合不包含来自数据库的实体,因为不使用延迟加载,也没有显式加载集合。Entity仅包含在 collection 初始化期间创建的新实体。SABDbContext DbSABS

当您调用IsModifed = true集合S实体框架时,会尝试将这两个新实体添加B到更改跟踪中。但它失败了,因为两个新B实体都具有相同的Id = 0

// This line tries to add to change tracking two new B entities with the same Id = 0.
// As a result it fails.
db.Entry(a).Collection(x => x.S).IsModified = true;

您可以从堆栈跟踪中看到实体框架尝试将B实体添加到IdentityMap

at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.ThrowIdentityConflict(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry, Boolean updateDuplicate)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetPropertyModified(IProperty property, Boolean changeState, Boolean isModified, Boolean isConceptualNull, Boolean acceptChanges)
at Microsoft.EntityFrameworkCore.ChangeTracking.NavigationEntry.SetFkPropertiesModified(InternalEntityEntry internalEntityEntry, Boolean modified)
at Microsoft.EntityFrameworkCore.ChangeTracking.NavigationEntry.SetFkPropertiesModified(Object relatedEntity, Boolean modified)
at Microsoft.EntityFrameworkCore.ChangeTracking.NavigationEntry.set_IsModified(Boolean value)

并且错误消息还告诉它无法跟踪B实体,Id = 0因为已经跟踪B了另一个具有相同的实体。Id


如何解决这个问题。

B要解决此问题,您应该删除在初始化S集合时创建实体的代码:

public ICollection<B> S { get; set; } = new List<B>();

S相反,您应该在创建的地方填写集合A。例如:

db.Add(new A {S = {new B(), new B()}});

如果您不使用延迟加载,则应显式加载S集合以将其项目添加到更改跟踪中:

// Use eager loading, for example.
A a = db.Set<A>().Include(x => x.S).Single();
db.Entry(a).Collection(x => x.S).IsModified = true;

为什么不添加而不是附加 B 的实例?

简而言之Detached,由于它们具有状态,因此它们被附加了被添加。

执行代码行后

var a = db.Set<A>().Single();

创建的实体实例B具有状态Detached。可以使用以下代码进行验证:

Console.WriteLine(db.Entry(a.S[0]).State);
Console.WriteLine(db.Entry(a.S[1]).State);

然后当你设置

db.Entry(a).Collection(x => x.S).IsModified = true;

EF 尝试添加B实体以进行更改跟踪。从EFCore的源代码中,您可以看到这导致我们使用带有下一个参数值的方法InternalEntityEntry.SetPropertyModified :

  • property- 我们的B实体之一,
  • changeState = true,
  • isModified = true,
  • isConceptualNull = false,
  • acceptChanges = true.

带有此类参数的此方法将实体的状态更改Detached BModified,然后尝试开始跟踪它们(参见第490 - 506 行)。因为B实体现在具有状态Modified,这导致它们被附加(未添加)。


推荐阅读