Dans l'exemple de code ci-dessous, j'obtiens l'exception suivante lors de l'exécution db.Entry(a).Collection(x => x.S).IsModified = true
:
System.InvalidOperationException: 'L'instance de type d'entité' B 'ne peut pas être suivie car une autre instance avec la valeur de clé' {Id: 0} 'est déjà en cours de suivi. Lorsque vous attachez des entités existantes, assurez-vous qu'une seule instance d'entité avec une valeur de clé donnée est attachée.
Pourquoi n'ajoute-t-il pas au lieu de joindre les instances de B?
Étrangement, la documentation de IsModified
ne spécifie pas d' InvalidOperationException
exception possible. Documentation invalide ou bug?
Je sais que ce code est étrange, mais je l'ai écrit uniquement pour comprendre comment le noyau ef fonctionne dans certains cas bizarres. Ce que je veux, c'est une explication, pas une solution.
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();
}
}
}
Réponses:
La raison de l'erreur dans le code fourni est la suivante.
Lorsque vous obtenez une entité créée à
A
partir de la base de données, sa propriétéS
est initialisée avec une collection qui contient deux nouveaux enregistrementsB
.Id
de chacune de ces nouvellesB
entités est égal à0
.Après avoir exécuté la ligne de code, la
var a = db.Set<A>().Single()
collectionS
d'entitéA
ne contient pas d'B
entités de la base de données, carDbContext Db
n'utilise pas de chargement différé et il n'y a pas de chargement explicite de la collectionS
. L'entitéA
contient uniquement les nouvellesB
entités créées lors de l'initialisation de la collectionS
.Lorsque vous appelez le framework d'entité de
IsModifed = true
collecte, essayezS
d'ajouter ces deux nouvelles entitésB
au suivi des modifications. Mais cela échoue car les deux nouvellesB
entités ont la mêmeId = 0
:Vous pouvez voir dans la trace de la pile que le framework d'entité essaie d'ajouter des
B
entités dansIdentityMap
:Et le message d'erreur indique également qu'il ne peut pas suivre l'
B
entité avecId = 0
car une autreB
entité avec la même choseId
est déjà suivie.Comment résoudre ce problème.
Pour résoudre ce problème, vous devez supprimer le code qui crée des
B
entités lors de l'initialisation de laS
collection:Au lieu de cela, vous devez remplir la
S
collection à l'endroit oùA
est créé. Par exemple:Si vous n'utilisez pas le chargement différé, vous devez explicitement charger la
S
collection pour ajouter ses éléments dans le suivi des modifications:Bref , ils sont attachés au lieu d'être ajoutés car ils ont de l'
Detached
état.Après avoir exécuté la ligne de code
les instances d'entité créées
B
ont un étatDetached
. Il peut être vérifié à l'aide du code suivant:Ensuite, lorsque vous définissez
EF essaie d'ajouter des
B
entités pour modifier le suivi. Du code source d' EFCore, vous pouvez voir que cela nous conduit à la méthode InternalEntityEntry.SetPropertyModified avec les valeurs d'argument suivantes:property
- une de nosB
entités,changeState = true
,isModified = true
,isConceptualNull = false
,acceptChanges = true
.Cette méthode avec de tels arguments change l'état des
Detached
B
entites enModified
, puis essaie de commencer leur suivi (voir lignes 490 - 506). Parce que lesB
entités ont maintenant un état,Modified
cela les conduit à être attachées (non ajoutées).la source
S
devrait être chargée explicitement, car le code fourni n'utilise pas le chargement paresseux. Bien sûr, EF a enregistré lesB
entités précédemment créées dans la base de données. Mais la ligne de codeA a = db.Set<A>().Single()
ne charge que l'entitéA
sans entité dans la collectionS
. Pour charger la collecte, unS
chargement plus rapide doit être utilisé. Je vais changer ma réponse pour inclure explicitement la réponse à la question "Pourquoi n'ajoute-t-elle pas au lieu de joindre les instances de B?".