No código de exemplo abaixo, recebo a seguinte exceção ao fazer db.Entry(a).Collection(x => x.S).IsModified = true
:
System.InvalidOperationException: 'A instância do tipo de entidade' B 'não pode ser rastreada porque outra instância com o valor da chave' {Id: 0} 'já está sendo rastreada. Ao anexar entidades existentes, verifique se apenas uma instância da entidade com um determinado valor de chave está anexada.
Por que não adiciona, em vez de anexar, as instâncias de B?
Estranhamente, a documentação para IsModified
não especifica InvalidOperationException
como uma possível exceção. Documentação inválida ou um bug?
Eu sei que esse código é estranho, mas eu o escrevi apenas para entender como o ef core funciona em alguns casos estranhos de egde. O que eu quero é uma explicação, não uma solução alternativa.
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();
}
}
}
Respostas:
A razão do erro no código fornecido é a seguir.
Quando você cria uma entidade
A
do banco de dados, sua propriedadeS
é inicializada com uma coleção que contém dois novos registrosB
.Id
de cada uma dessas novasB
entidades é igual a0
.Após a execução da linha de
var a = db.Set<A>().Single()
coleção de códigoS
da entidade,A
ela não contémB
entidades do banco de dados, porqueDbContext Db
não usa carregamento lento e não há carregamento explícito da coleçãoS
. A entidadeA
contém apenas novasB
entidades que foram criadas durante a inicialização da coleçãoS
.Quando você chama
IsModifed = true
aS
estrutura da entidade de coleção , tenta adicionar esses dois novos serviçosB
ao controle de alterações. Mas falha porque ambas as novasB
entidades têm o mesmoId = 0
:Você pode ver no rastreamento da pilha que a estrutura da entidade tenta adicionar
B
entidades aoIdentityMap
:E a mensagem de erro também informa que ele não pode rastrear a
B
entidadeId = 0
porque outraB
entidade com o mesmoId
já está rastreada.Como resolver este problema.
Para resolver esse problema, você deve excluir o código que cria
B
entidades ao inicializar aS
coleção:Em vez disso, você deve preencher a
S
coleção no local em queA
é criada. Por exemplo:Se você não usar o carregamento lento, deverá carregar explicitamente a
S
coleção para adicionar seus itens ao rastreamento de alterações:Em suma , eles são anexados a serem adicionados porque têm
Detached
estado.Depois de executar a linha de código
instâncias criadas da entidade
B
têm estadoDetached
. Pode ser verificado usando o próximo código:Então, quando você define
O EF tenta adicionar
B
entidades para alterar o rastreamento. No código-fonte do EFCore, você pode ver que isso nos leva ao método InternalEntityEntry.SetPropertyModified com os próximos valores de argumento:property
- uma das nossasB
entidades,changeState = true
,isModified = true
,isConceptualNull = false
,acceptChanges = true
.Esse método com esses argumentos altera o estado dos
Detached
B
atributos paraModified
e tenta iniciar o rastreamento deles (consulte as linhas 490 - 506). Como asB
entidades agora têm estado,Modified
isso as leva a serem anexadas (não adicionadas).fonte
S
deve ser carregada explicitamente, porque o código fornecido não usa carregamento lento. Obviamente, a EF salvouB
entidades criadas anteriormente no banco de dados. Mas a linha de códigoA a = db.Set<A>().Single()
carrega apenas entidadesA
sem entidades na coleçãoS
. Para carregar a coleção,S
deve-se usar um carregamento rápido. Vou mudar meu asnwer para incluir explicitamente a resposta à pergunta "Por que não adiciona em vez de anexar as instâncias de B?".