Wie entferne ich einen bis viele verwandte Datensätze in der ersten EF-Code-Datenbank?

70

Nun, ich habe ein zu viele verwandte Modelle:

public class Parent
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Child> Children { get; set; }
}

public class Child
{
    public int Id { get; set; }
    public string ChildName { get; set; }
}

Ich möchte Parent.Childrenverwandte untergeordnete Entitäten löschen und aus der Datenbank entfernen. Ich habe es bereits versucht:

Datenbankkontextklasse:

modelBuilder.Entity<Parent>()
            .HasMany(p => p.Children)
            .WithOptional()
            .WillCascadeOnDelete(true);

Dies funktioniert einwandfrei, aber ich habe immer noch redundante Datensätze in der Datenbank mit Parent_Id = nullFeldern, wenn ich dies tue

parent.Children.Clear();
repository.InsertOrUpdate(parent);

in meiner Repository-Klasse. Das gleiche Verhalten ist auch, wenn ich:

modelBuilder.Entity<Parent>()
            .HasMany(pr => pr.Children)
            .WithOptional(ri => ri.Parent)
            .WillCascadeOnDelete(true);

mit zusätzlicher ParentEigenschaft in der ChildKlasse

public class Child
{
    ...
    public Parent Parent { get; set; }
    ...
}

oder wenn ich es tue

modelBuilder.Entity<Child>()
            .HasOptional(p => p.Parent)
            .WithMany(p => p.Children)
            .HasForeignKey(p => p.Parent_Id)
            .WillCascadeOnDelete(true);

mit zusätzlicher Parent_Id-Eigenschaft in der ChildKlasse

public class Child
{
     ...
     public int Parent_Id { get; set; }
     ...
}

Wie kann ich das Löschen von Kaskaden korrekt konfigurieren? Oder wie soll ich diese untergeordneten Entitäten entfernen? Ich nehme an, das ist eine Gelegenheitsaufgabe, aber mir fehlt nur etwas.

Dmytro
quelle
Kannst du noch mehr Code posten? Vielleicht der volle Inhalt von dir OnModelCreating()? Wenn ich Ihre Entitäten und Ihren ersten Zuordnungsversuch kopiere und einfüge (und Idals Schlüsseleigenschaft für beide Entitäten festlege ), werden Löschvorgänge für mich korrekt kaskadiert.
Jeremy Todd

Antworten:

69

Das kaskadierende Löschen hat hier keine Auswirkung, da Sie das nicht löschen, parentsondern nur aufrufen InsertOrUpdate. Das richtige Verfahren besteht darin, die Kinder einzeln zu löschen, wie zum Beispiel:

using (var context = new MyContext())
{
    var parent = context.Parents.Include(p => p.Children)
        .SingleOrDefault(p => p.Id == parentId);

    foreach (var child in parent.Children.ToList())
        context.Children.Remove(child);

    context.SaveChanges();
}
Slauma
quelle
Sicherlich müssen Sie mit Cascade Delete on die Kinder nicht einzeln löschen?
Kirsten Greed
7
@kirsteng: Kaskadierendes Löschen bedeutet, dass die untergeordneten Elemente gelöscht werden, wenn Sie das übergeordnete Element löschen. In dieser Frage wird jedoch kein Elternteil gelöscht. Das kaskadierende Löschen gilt daher nicht für dieses Szenario.
Slauma
22
Anstatt alle "untergeordneten" Objekte zu context.Children.RemoveRange(parent.Children.ToArray())suchen , sagen Sie einfach so, dass der DbContext nicht jedes Mal, wenn Sie Remove aufrufen, so viel Arbeit erledigen muss. Dies ist möglicherweise kein großes Leistungsproblem beim Löschen, aber ich habe einen großen Unterschied beim Hinzufügen von Elementen nach dem anderen festgestellt, als ich das Hinzufügen von 100.000 Datensätzen mithilfe context.Children.Add(child)einer for-Schleife getestet habe .
C. Tewalt
1
Kaskadenlöschung funktioniert in dieser Situation, zumindest bei Verwendung von EF Core :)
Konrad
1
@Konrad Danke für den Hinweis. In meinem Fall musste ich das DB-Schema ändern ON DELETE CASCADEund DB-Modelle erneut generieren, um DeleteBehavior.Cascadesie zu haben .
Jari Turkia
108

In EF6 ist eine schnellere Möglichkeit, die Operation durchzuführen, ...

 context.Children.RemoveRange(parent.Children)
Sam Sippe
quelle
Es ist eine schnellere Methode zum Codieren, aber in Bezug auf die Leistung wirklich schneller? Wenn ich alle untergeordneten Elemente mit löschen möchte parent.Id == 10und meine ChildKlasse 100 Felder hat und noch nicht in den Speicher geladen wurde, scheint die Verwendung ineffizient zu sein, RemoveRangeda das Programm parent.Childrenstandardmäßig mit allen 100 Feldern geladen werden muss .
VCD
2
@VCD ist schneller als der Aufruf von context.Children.Remove (child) für jedes Kind, aber nicht so schnell wie das Ausführen von sql "DELETE FROM Children where parentId = @ parentId"
Sam Sippe
Wie würden Sie vorgehen, um die Eltern zu finden, wenn die einzige Information, die Sie haben, die Kinder-ID ist?
guyfromfargo
@guyfromfargo in EF so etwas wievar child = context.Children.Single(f=>f.Id==childId); var parent = context.Parents.Single(f=>f.Id==child.ParentId);
Sam Sippe
2

Versuchen Sie, zu zu wechseln

 public virtual ICollection<Child> Children { get; set; }

weil virtuell benötigt wird, um faul zu laden. wie hier erklärt

Ich denke, deine Eltern.Kinder.clear funktionieren nicht, weil die Kinder nicht geladen wurden

Kirsten Gier
quelle
1
In meinem Fall (ASP.NET Core 2 / EF Core) habe ich getestet und dies war nicht die Lösung
hamid reza
1

Wenn sich Ihr Objekt selbst referenziert, können Sie mit der folgenden Methode sowohl viele-zu-viele als auch eins-zu-viele untergeordnete Objekte löschen. Denken Sie daran, danach db.SaveChanges () aufzurufen :)

[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
    Object obj = this.db.Objects.Find(id);
    this.DeleteObjectAndChildren(obj);
    this.db.Objects.Remove(obj);
    this.db.SaveChanges();
    return this.Json(new { success = true });
}

/// <summary>
/// This deletes an object and all children, but does not commit changes to the db.
///  - MH @ 2016/08/15 14:42
/// </summary>
/// <param name="parent">
/// The object.
/// </param>
private void DeleteObjectAndChildren(Object parent)
{
    // Deletes One-to-Many Children
    if (parent.Things != null && parent.Things.Count > 0)
    {
        this.db.Things.RemoveRange(parent.Things);
    }

    // Deletes Self Referenced Children
    if (parent.Children != null && parent.Children.Count > 0)
    {
        foreach (var child in parent.Children)
        {
            this.DeleteObjectAndChildren(child);
        }

        this.db.Objects.RemoveRange(parent.Children);
    }
}
Matthew Hudson
quelle