Unerwartete InvalidOperationException beim Versuch, die Beziehung über den Standardwert der Eigenschaft zu ändern


10

Im folgenden Beispielcode wird dabei die folgende Ausnahme angezeigt db.Entry(a).Collection(x => x.S).IsModified = true:

System.InvalidOperationException: 'Die Instanz des Entitätstyps' B 'kann nicht verfolgt werden, da bereits eine andere Instanz mit dem Schlüsselwert' {Id: 0} 'verfolgt wird. Stellen Sie beim Anhängen vorhandener Entitäten sicher, dass nur eine Entitätsinstanz mit einem bestimmten Schlüsselwert angehängt ist.

Warum werden die Instanzen von B nicht hinzugefügt, anstatt sie anzuhängen?

Seltsamerweise wird in der Dokumentation für IsModifiednicht InvalidOperationExceptionals mögliche Ausnahme angegeben. Ungültige Dokumentation oder ein Fehler?

Ich weiß, dass dieser Code seltsam ist, aber ich habe ihn nur geschrieben, um zu verstehen, wie ef core in einigen seltsamen Fällen funktioniert. Was ich will, ist eine Erklärung, keine Umgehung.

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();
        }
    }
}

Wie hängen A und B zusammen? Was ist die Beziehungseigenschaft?
Sam

Antworten:


8

Der Grund für den Fehler im bereitgestellten Code ist folgender.

Wenn Sie eine Entität Aaus der Datenbank erstellen, Swird ihre Eigenschaft mit einer Sammlung initialisiert, die zwei neue Datensätze enthält B. Idvon jeder dieser neuen BEntitäten ist gleich 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 {} };

Nach dem Ausführen der Codezeile enthält die var a = db.Set<A>().Single()Sammlung Svon Entitäten Akeine BEntitäten aus der Datenbank, da DbContext Dbkein verzögertes Laden verwendet wird und die Sammlung nicht explizit geladen wird S. Entität Aenthält nur neue BEntitäten, die während der Initialisierung der Sammlung erstellt wurden S.

Wenn Sie ein Framework IsModifed = truefür Sammlungsentitäten aufrufen S, wird versucht, diese beiden neuen Entitäten Bzur Änderungsverfolgung hinzuzufügen . Dies schlägt jedoch fehl, da beide neuen BEntitäten dasselbe haben 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;

An der Stapelverfolgung können Sie erkennen, dass das Entitätsframework versucht, BEntitäten zu folgenden Elementen hinzuzufügen 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)

Die Fehlermeldung besagt auch, dass eine Entität nicht verfolgt werden kann B, Id = 0da bereits eine andere BEntität mit derselben Entität Idverfolgt wird.


So lösen Sie dieses Problem.

Um dieses Problem zu beheben, sollten Sie Code löschen, der Bbeim Initialisieren der SSammlung Entitäten erstellt :

public ICollection<B> S { get; set; } = new List<B>();

Stattdessen sollten Sie die SSammlung an der Stelle füllen , an der sie Aerstellt wurde. Zum Beispiel:

db.Add(new A {S = {new B(), new B()}});

Wenn Sie kein verzögertes Laden verwenden, sollten Sie die SSammlung explizit laden , um ihre Elemente zur Änderungsverfolgung hinzuzufügen:

// Use eager loading, for example.
A a = db.Set<A>().Include(x => x.S).Single();
db.Entry(a).Collection(x => x.S).IsModified = true;

Warum werden die Instanzen von B nicht hinzugefügt, anstatt sie anzuhängen?

Kurz gesagt , sie werden angehängt, weil sie einen DetachedStatus haben.

Nach dem Ausführen der Codezeile

var a = db.Set<A>().Single();

erstellte Instanzen von Entitäten Bhaben Status Detached. Es kann mit dem nächsten Code überprüft werden:

Console.WriteLine(db.Entry(a.S[0]).State);
Console.WriteLine(db.Entry(a.S[1]).State);

Dann, wenn Sie einstellen

db.Entry(a).Collection(x => x.S).IsModified = true;

EF versucht, BEntitäten hinzuzufügen , um die Nachverfolgung zu ändern. Aus dem Quellcode von EFCore können Sie ersehen , dass dies uns zur Methode InternalEntityEntry.SetPropertyModified mit den nächsten Argumentwerten führt:

  • property- eine unserer BEinheiten,
  • changeState = true,
  • isModified = true,
  • isConceptualNull = false,
  • acceptChanges = true.

Diese Methode mit solchen Argumenten ändert den Status der Detached BEntitäten in Modifiedund versucht dann, die Verfolgung für sie zu starten (siehe Zeilen 490 - 506). Da BEntitäten jetzt den Status Modifiedhaben, werden sie angehängt (nicht hinzugefügt).


Wo ist die Antwort für "Warum wird nicht hinzugefügt, anstatt die Instanzen von B anzuhängen?" Sie sagen "es schlägt fehl, weil beide neuen B-Entitäten dieselbe ID = 0 haben". Ich denke, es ist falsch, weil ef core sowohl mit 1 als auch mit 2 IDs speichert. Ich denke nicht, dass es die richtige Antwort auf diese Frage ist
DIlshod K

@DIlshod K Danke für den Kommentar. Im Abschnitt "So beheben Sie dieses Problem" habe ich geschrieben, dass die Sammlung Sexplizit geladen werden sollte, da der bereitgestellte Code kein verzögertes Laden verwendet. Natürlich hat EF zuvor erstellte BEntitäten in der Datenbank gespeichert. Die Codezeile A a = db.Set<A>().Single()lädt jedoch nur Entitäten Aohne Entitäten in der Sammlung S. Zum Laden der Sammlung sollte Seifriges Laden verwendet werden. Ich werde meine Antwort so ändern, dass sie explizit die Antwort auf die Frage "Warum wird nicht hinzugefügt, anstatt die Instanzen von B anzuhängen?" Einfügt.
Iliar Turdushev
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.