아래 샘플 코드에서 수행 할 때 다음 예외가 발생합니다 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
가 포함 되지 않습니다 . 엔터티 에는 컬렉션을 초기화하는 동안 만들어진 새 엔터티 만 포함 됩니다.B
DbContext Db
S
A
B
S
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
그들에게 부착이 리드를 (추가되지 않음).