无法跟踪实体类型“TestType”的实例,因为已经跟踪了具有相同密钥的此类型的另一个实例

时间:2016-12-16 09:51:52

标签: c# entity-framework-core

我在尝试时遇到以下异常:

    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

似乎是的......已经跟踪了一些实体。似乎我必须在改变跟踪器上投入更多的东西,然后决定做什么。

但是我无法相信我是第一个做这样的事情,没有在谷歌上找到任何东西。

1 个答案:

答案 0 :(得分:0)

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

嗯......然后我在流畅的迁移中搜索并发现了这个:

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

我刚刚做过这个(但从未测试过 - 对我感到羞耻 - )我认为它的EF6相当于索引属性...因为这就是我想要的功能!

然后我用Google搜索并创建了一些新内容:

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

现在我有我想要的东西!

我还将schoolclass,subject和schoolyear实例的分配删除到testTypesToRemove,这也创建了一些奇怪的东西......

该代码现在有效:

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