W przykładowym kodzie poniżej otrzymuję następujący wyjątek db.Entry(a).Collection(x => x.S).IsModified = true
:
System.InvalidOperationException: „Instancja typu jednostki„ B ”nie może być śledzona, ponieważ inna instancja o wartości klucza„ {Id: 0} ”jest już śledzona. Podczas dołączania istniejących encji upewnij się, że dołączona jest tylko jedna instancja encji o danej wartości klucza.
Dlaczego nie dodaje zamiast instancji B?
O dziwo dokumentacja IsModified
nie określa InvalidOperationException
jako możliwego wyjątku. Nieprawidłowa dokumentacja czy błąd?
Wiem, że ten kod jest dziwny, ale napisałem go tylko po to, aby zrozumieć, jak działa ef core w niektórych dziwnych przypadkach egde. Chcę wyjaśnienia, a nie obejścia.
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();
}
}
}
c#
language-lawyer
entity-framework-core-3.1
Supremum
źródło
źródło
Odpowiedzi:
Przyczyna błędu w podanym kodzie jest następująca.
Po utworzeniu encji
A
z bazy danych jej właściwośćS
jest inicjowana kolekcją zawierającą dwa nowe rekordyB
.Id
każdego z tych nowychB
bytów jest równy0
.Po wykonaniu wiersza kodu
var a = db.Set<A>().Single()
zbiórS
encjiA
nie zawieraB
encji z bazy danych, ponieważDbContext Db
nie używa leniwego ładowania i nie ma jawnego ładowania kolekcjiS
. JednostkaA
zawiera tylko noweB
jednostki, które zostały utworzone podczas inicjowania kolekcjiS
.Podczas wywoływania środowiska encji
IsModifed = true
kolekcjiS
próbuje dodać te dwa nowe elementyB
do śledzenia zmian. Ale zawodzi, ponieważ oba noweB
podmioty mają to samoId = 0
:Ze śladu stosu widać, że struktura encji próbuje dodać
B
encje doIdentityMap
:Komunikat o błędzie mówi również, że nie może śledzić
B
encji,Id = 0
ponieważ innaB
encja z tym samymId
jest już śledzona.Jak rozwiązać ten problem.
Aby rozwiązać ten problem, należy usunąć kod, który tworzy
B
jednostki podczas inicjowaniaS
kolekcji:Zamiast tego należy wypełnić
S
kolekcję w miejscu, w którymA
został utworzony. Na przykład:Jeśli nie używasz leniwego ładowania, należy jawnie załadować
S
kolekcję, aby dodać jej elementy do śledzenia zmian:Krótko mówiąc , są dołączeni natychmiast po dodaniu, ponieważ mają
Detached
stan.Po wykonaniu wiersza kodu
utworzone wystąpienia encji
B
mają stanDetached
. Można to zweryfikować za pomocą następnego kodu:Potem, kiedy ustawisz
EF próbuje dodać
B
podmioty, aby zmienić śledzenie. Z kodu źródłowego EFCore widać, że prowadzi nas to do metody InternalEntityEntry.SetPropertyModified z następnymi wartościami argumentów:property
- jeden z naszychB
podmiotów,changeState = true
,isModified = true
,isConceptualNull = false
,acceptChanges = true
.Ta metoda z takimi argumentami zmienia stan
Detached
B
entitesModified
, a następnie próbuje rozpocząć ich śledzenie (patrz wiersze 490 - 506). PonieważB
jednostki mają teraz stan,Modified
prowadzi to do ich przyłączenia (nie dodania).źródło
S
powinna być ładowana jawnie, ponieważ podany kod nie używa leniwego ładowania. Oczywiście EF zapisał wcześniej utworzoneB
jednostki w bazie danych. Ale wiersz koduA a = db.Set<A>().Single()
ładuje tylko encjęA
bez encji w kolekcjiS
. Aby załadować kolekcję,S
należy użyć szybkiego ładowania. Zmienię moją odpowiedź, aby wyraźnie zawierała odpowiedź na pytanie „Dlaczego nie dodaje się zamiast dołączać wystąpienia B?”.