[c#] 속성 기본값을 통해 관계를 변경하려고 할 때 예기치 않은 InvalidOperationException이 발생했습니다

아래 샘플 코드에서 수행 할 때 다음 예외가 발생합니다 db.Entry(a).Collection(x => x.S).IsModified = true.

System.InvalidOperationException : ‘키 값이'{Id : 0} ‘인 다른 인스턴스가 이미 추적 중이므로’엔터티 유형 ‘B’의 인스턴스를 추적 할 수 없습니다. 기존 엔터티를 연결할 때는 지정된 키 값을 가진 엔터티 인스턴스가 하나만 연결되어 있는지 확인하십시오.

B의 인스턴스를 첨부하는 대신 왜 추가하지 않습니까?

이상하게도 설명서 는 가능한 예외로 IsModified지정되지 않았습니다 InvalidOperationException. 잘못된 문서 또는 버그?

나는이 코드가 이상하다는 것을 알고 있지만, 이상한 egde 사례에서 ef core가 어떻게 작동하는지 이해하기 위해서만 작성했습니다. 내가 원하는 것은 설명이 아니라 해결 방법입니다.

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



답변

제공된 코드의 오류 이유는 다음과 같습니다.

A데이터베이스에서 엔터티 를 만들면 속성 S이 두 개의 새 레코드가 포함 된 컬렉션으로 초기화됩니다 B. Id이 새로운 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()콜렉션 S에 데이터베이스 의 엔티티 A가 포함 되지 않습니다 . 엔터티 에는 컬렉션을 초기화하는 동안 만들어진 새 엔터티 만 포함 됩니다.BDbContext 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상태 가 있기 때문에 추가되는 insted 첨부 됩니다.

코드 라인을 실행 한 후

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

생성 된 엔티티 인스턴스 B는 state 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 B하는 entites Modified, 다음 (선 참조 그들을 위해 추적을 시작하려고합니다 (490) – 506). 때문에 B실체가 지금 상태가 Modified그들에게 부착이 리드를 (추가되지 않음).


답변