【问题标题】:The instance of entity type 'TestType' cannot be tracked because another instance of this type with the same key is already being tracked无法跟踪实体类型“TestType”的实例,因为已在跟踪具有相同键的此类型的另一个实例
【发布时间】:2016-12-16 23:44:40
【问题描述】:

当我尝试这样做时遇到以下异常:

    context.Entry(testType).State = EntityState.Modified;


System.InvalidOperationException: The instance of entity type 'TestType' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (i.e. if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context.
   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.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState entityState, Boolean acceptChanges)
   at Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry.set_State(EntityState value)

我没有看到任何代码已经跟踪了具有相同键的另一个 TestType 实例!

数据库中的测试类型通过.AsNoTracking();加载

那么在这段代码中,TestType 的实例与我执行 RemoveRange/AddRange 操作的键相同吗?那意味着我的 TestTypeComparer 坏了?

我在这里尝试做的是让用户一次性批量保存更改/添加/删除的实体:

 public async Task<IEnumerable<TestType>> SaveTestTypesAsync(List<TestType> testTypes, int schoolyearId, int schoolclassId, int subjectId)
        {
            var testTypesFromDatabase = await context.TestTypes
                                          .Include(t => t.Subject)
                                          .Include(s => s.Schoolclass)
                                          .Where(p =>
                                          p.Schoolclass.Id == schoolclassId &&
                                          p.Subject.Id == subjectId)
                                          .AsNoTracking()
                                          .ToListAsync();

            var schoolclass = new Schoolclass { Id = schoolclassId };
            var subject = new Subject { Id = subjectId };
            var schoolyear = new Schoolyear { Id = schoolyearId };
            foreach (var testType in testTypes)
            {
                testType.Schoolclass = schoolclass;
                testType.Subject = subject;
                testType.Schoolyear = schoolyear;
            }

            var testTypesToRemove = testTypesFromDatabase.Except(testTypes, new TestTypeComparer()).ToList();
            context.TestTypes.RemoveRange(testTypesToRemove);

            var testTypesToAdd = testTypes.Where(t => t.Id == 0).ToList();  // 
            context.TestTypes.AddRange(testTypesToAdd);

            var modifiedTestTypesToUpdate = testTypes.Except(testTypesToAdd.Concat(testTypesToRemove).ToList(), new TestTypeComparer()).ToList();
            foreach (var testType in modifiedTestTypesToUpdate)
            {
                context.Entry(testType).State = EntityState.Modified;
            }

            context.Attach(schoolclass);      
            context.Attach(subject);
            context.Attach(schoolyear);

            await context.SaveChangesAsync();

            return await this.GetTestTypesConfigurationAsync(schoolclassId, subjectId);
        }


public class TestTypeComparer : IEqualityComparer<TestType>
{
    public bool Equals(TestType x, TestType y)
    {
        return x.Id == y.Id;
    }

    public int GetHashCode(TestType obj)
    {
        return obj.Id.GetHashCode();
    }
}

public class TestType
    {
        public TestType()
        {
            Tests = new HashSet<Test>();
        }

        public int Id { get; set; }
        public string Name { get; set; }
        public int Weight { get; set; }
        public ISet<Test> Tests { get; set; }
        public Schoolyear Schoolyear { get; set; }  
        public Schoolclass Schoolclass { get; set; }
        public int SchoolclassId { get; set; }
        public Subject Subject { get; set; }
        public int SubjectId { get; set; }
        public int SchoolyearId { get; set; }
    }

任何人都可以帮助我,我无法发现具有相同密钥的双重跟踪实体。

我只是假设问题与我如何确定添加/修改/删除的实体有关。

更新

我现在已经在为所有 TestTypesToUpdate 设置 State.Modified 之前记录了所有跟踪器更改:

State: Deleted | Type: TestType | Id-Value: 12
State: Unchanged | Type: Schoolclass | Id-Value: 1
State: Unchanged | Type: TestType | Id-Value: 8
State: Unchanged | Type: Subject | Id-Value: 1
State: Deleted | Type: TestType | Id-Value: 13
State: Added | Type: TestType | Id-Value: -2147482647
State: Detached | Type: Schoolclass | Id-Value: 1
State: Added | Type: Schoolyear | Id-Value: 1
State: Detached | Type: Subject | Id-Value: 1
State: Added | Type: TestType | Id-Value: -2147482646

似乎是的......一些实体已经被追踪。看来我必须在更改跟踪器上投入更多的 if/else,然后再决定要做什么。

但我不敢相信我是第一个这样做的人,在谷歌上没有找到任何东西。

【问题讨论】:

  • IMO 这是因为上下文。您应该在每个查询中处理并重新初始化。但是您不止一次使用相同的上下文。
  • 在您看来,从技术上讲,什么是多次使用?您是否在方法结束时谈论 Get 调用?我可以删除那个 Get Call 而不会产生任何影响。
  • "context.TestTypes" 上下文来自哪里?每次调用时“上下文”都是新的吗? Dispose
  • 这个上下文被注入到每个 http 调用的存储库构造函数中。它只是创建了一次!

标签: c# entity-framework-core


【解决方案1】:

在考虑具有相同键的多个实例问题后,我再次重新排列了代码,直到再次出现错误,我无法将 testTypeToUpdate 的状态设置为 state.Modified 因为 SchoolclassId 是一部分一把钥匙。

嗯...然后我在我的流利迁移中搜索并找到了这个:

modelBuilder.Entity<TestType>().HasAlternateKey(x => new { x.Name, x.SchoolclassId, x.SubjectId });

我不久前做过这个(但从未测试过——真丢脸——),我认为它相当于索引属性的 EF6...因为这就是我想要的功能!

然后我用谷歌搜索并创建了一些新的东西:

modelBuilder.Entity<TestType>().HasIndex(p => new { p.Name, p.SchoolclassId, p.SubjectId} ).IsUnique();

现在我得到了我想要的!

我还删除了分配给 testTypesToRemove 的 schoolclass、subject 和 schoolyear 实例,这也产生了一些奇怪的东西......

该代码现在可以工作了:

   public async Task<IEnumerable<TestType>> SaveTestTypesAsync(List<TestType> testTypes, int schoolyearId, int schoolclassId, int subjectId)
        {
            var testTypesFromDatabase = await context.TestTypes
                                          .Include(t => t.Subject)
                                          .Include(s => s.Schoolclass)
                                          .Where(p =>
                                          p.Schoolclass.Id == schoolclassId &&
                                          p.Subject.Id == subjectId)
                                          .AsNoTracking()
                                          .ToListAsync();

            var schoolclass = new Schoolclass { Id = schoolclassId };
            var subject = new Subject { Id = subjectId };
            var schoolyear = new Schoolyear { Id = schoolyearId };

            // Make the navigation properties available during SaveChanges()
            context.Attach(schoolclass);
            context.Attach(subject);
            context.Attach(schoolyear);

            // DELETE
            var testTypesToRemove = testTypesFromDatabase.Except(testTypes, new TestTypeComparer()).ToList();
            context.TestTypes.RemoveRange(testTypesToRemove);

            // ADD
            var testTypesToAdd = testTypes.Where(t => t.Id == 0).ToList();  // 
            foreach (var testType in testTypesToAdd)
            {
                testType.Schoolclass = schoolclass;
                testType.Subject = subject;
                testType.Schoolyear = schoolyear;
            }
            context.TestTypes.AddRange(testTypesToAdd);

            // UPDATE
            var modifiedTestTypesToUpdate = testTypes.Except(testTypesToAdd.Concat(testTypesToRemove).ToList(), new TestTypeComparer()).ToList();
            foreach (var testType in modifiedTestTypesToUpdate)
            {
                testType.Schoolclass = schoolclass;
                testType.Subject = subject;
                testType.Schoolyear = schoolyear;
            }   
            context.UpdateRange(modifiedTestTypesToUpdate);

            await context.SaveChangesAsync();

            return await this.GetTestTypesConfigurationAsync(schoolclassId, subjectId);
        }

【讨论】:

    猜你喜欢
    • 2016-08-19
    • 2022-01-02
    • 1970-01-01
    • 2023-01-03
    • 1970-01-01
    • 1970-01-01
    • 2018-06-20
    • 1970-01-01
    相关资源
    最近更新 更多