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


10

아래 샘플 코드에서 수행 할 때 다음 예외가 발생합니다 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와 B는 어떻게 관련되어 있습니까? 관계 속성이란 무엇입니까?

답변:


8

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

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그들에게 부착이 리드를 (추가되지 않음).


"B의 인스턴스를 첨부하는 대신 왜 추가하지 않습니까?"에 대한 답은 어디에 있습니까? "새로운 B 엔터티가 동일한 Id = 0을 가지고 있기 때문에 실패합니다"라고 말하는 것입니다. ef core가 1과 2의 id로 모두 저장하기 때문에 잘못되었다고 생각합니다. 나는
이것이이

@DIlshod K 댓글 주셔서 감사합니다. "이 문제를 해결하는 방법"섹션에서 S제공된 코드는 지연 로딩을 사용하지 않기 때문에 컬렉션 을 명시 적으로로드해야한다고 썼습니다 . 물론 EF는 이전에 생성 된 B엔터티를 데이터베이스에 저장했습니다. 그러나 코드 줄은 컬렉션에 A a = db.Set<A>().Single()엔터티가 A없는 엔터티 만로드합니다 S. 수집을 S열망 하려면 더 많은 로딩을 사용해야합니다. "B의 인스턴스를 연결하는 대신 왜 추가하지 않습니까?"라는 질문에 대한 답변을 명시 적으로 포함하도록 asnwer를 변경하겠습니다.
Iliar Turdushev
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.