Filter alle Navigations-Eigenschaften, bevor Sie geladen werden (lazy oder eager) in den Speicher
Für zukünftige Besucher: für EF6 sind Sie wahrscheinlich besser dran, mit Filter, zum Beispiel über das Projekt: https://github.com/jbogard/EntityFramework.Filters
In der Anwendung, die wir bauen, wir wenden die "soft-löschen" - Muster, bei dem jede Klasse hat eine 'Gelöscht' bool. In der Praxis, jede Klasse erbt einfach von dieser Basisklasse:
public abstract class Entity
{
public virtual int Id { get; set; }
public virtual bool Deleted { get; set; }
}
Geben Sie ein kurzes Beispiel, angenommen ich habe die Klassen GymMember
und Workout
:
public class GymMember: Entity
{
public string Name { get; set; }
public virtual ICollection<Workout> Workouts { get; set; }
}
public class Workout: Entity
{
public virtual DateTime Date { get; set; }
}
Wenn ich hol die Liste der Fitness-Studio-Mitglieder aus der Datenbank, ich kann sicherstellen, dass keiner der 'gelöscht' Fitness-Studio-Mitgliedern abgerufen werden, wie hier:
var gymMembers = context.GymMembers.Where(g => !g.Deleted);
Wenn ich allerdings Durchlaufen diese Fitness-Studio-Mitglieder, Ihre Workouts
werden aus der Datenbank geladen, ohne jede Rücksicht für Ihre Deleted
Flagge. Zwar kann ich die Schuld Entity Framework nicht Kommissionierung bis auf diese, ich möchte zu konfigurieren oder abfangen lazy-Eigenschaft laden irgendwie so, dass gelöschte Navigations-Eigenschaften sind noch nie geladen.
Ich habe gehen durch, meine Optionen, aber Sie scheinen rar:
- Gehen zu
Database First
und bedingte Zuordnung für jedes Objekt, für jeden eins-zu-viele-Eigenschaft.
Dies ist einfach keine option, da wäre es zu viel manuelle Arbeit. (Unsere Anwendung ist riesig und immer grösser jeden Tag). Wir wollen auch nicht zu geben, die Vorteile der Verwendung von Code First (von denen gibt es viele)
Wieder, keine option. Diese Konfiguration ist nur verfügbar, pro Person. Immer eifrig laden Einrichtungen würde auch zu verhängen eine schwerwiegende Strafe.
- Anwendung des Ausdrucks Besucher Muster, das automatisch injiziert
.Where(e => !e.Deleted)
überall, es findet eineIQueryable<Entity>
wie beschrieben hier und hier.
Ich tatsächlich getestet, die in einer proof-of-concept-Anwendung, und es klappte wunderbar.
Dies war eine sehr interessante option, aber leider, es nicht zu gelten-Filterung verzögert geladen Navigations-Eigenschaften. Dies ist offensichtlich, wie diejenigen, die faul Eigenschaften erscheinen nicht im Ausdruck/Abfrage und als solche kann nicht ersetzt werden. Ich Frage mich, ob Entity Framework erlauben würde, für eine Injektion Punkt irgendwo in Ihrer DynamicProxy
Klasse, lädt der lazy-Eigenschaften.
Ich habe auch Angst für die anderen Folgen, wie die Möglichkeit der Zerschlagung der Include
Mechanismus in EF.
- Schreiben Sie eine benutzerdefinierte Klasse, die ICollection implementiert, sondern filtert die
Deleted
Entitäten automatisch.
War dies tatsächlich mein Erster Ansatz. Die Idee wäre die Verwendung von background-Eigenschaft für jede Sammlung-Eigenschaft, die intern verwendet eine benutzerdefinierte Auflistungsklasse:
public class GymMember: Entity
{
public string Name { get; set; }
private ICollection<Workout> _workouts;
public virtual ICollection<Workout> Workouts
{
get { return _workouts ?? (_workouts = new CustomCollection()); }
set { _workouts = new CustomCollection(value); }
}
}
Dieser Ansatz ist eigentlich nicht schlecht, ich habe noch einige Probleme mit ihm:
-
Es dennoch lädt alle
Workout
s in den Speicher und Filter, dieDeleted
diejenigen, wenn die Eigenschaft setter ist der hit. Meiner bescheidenen Meinung nach ist dies viel zu spät. -
Es ist eine logische Diskrepanz zwischen durchgeführten Abfragen und die Daten, die geladen wird.
Bild ein Szenario, wo ich möchte eine Liste der Fitness-Studio-Mitglieder, haben Sie ein workout-seit letzter Woche:
var gymMembers = context.GymMembers.Where(g => g.Workouts.Any(w => w.Date >= DateTime.Now.AddDays(-7).Date));
Diese Abfrage zurückgeben kann, eine Fitness-Studio-Mitglied, dass nur Trainingseinheiten, die gelöscht werden, sondern auch das Prädikat erfüllen. Sind Sie einmal in den Speicher geladen, es scheint, als wenn das Fitness-Studio Mitglied hat keine workouts überhaupt!
Man könnte sagen, die Entwickler sollten sich bewusst sein, der Deleted
und stets in seinem Abfragen, aber das ist etwas, das würde ich wirklich gerne vermeiden. Vielleicht die ExpressionVisitor anbieten könnte, hier die Antwort wieder.
- Es ist eigentlich unmöglich, daneben eine navigation, die Eigenschaft als
Deleted
bei der Verwendung der customcollection hinzuzufügen.
Stellen Sie sich dieses Szenario:
var gymMember = context.GymMembers.First();
gymMember.Workouts.First().Deleted = true;
context.SaveChanges();`
Würden Sie erwarten, dass die entsprechenden Workout
Datensatz in der Datenbank aktualisiert, und Sie würden falsch sein! Da die gymMember
wird geprüft, indem die ChangeTracker
für alle änderungen, die Eigenschaft gymMember.Workouts
plötzlich zurück 1 weniger Training. Das ist, weil customcollection hinzuzufügen filtert automatisch gelöscht Instanzen, erinnern Sie sich? So, jetzt Entity Framework denkt, dass das Training muss gelöscht werden, und EF wird versuchen, die FK auf null gesetzt wird, oder tatsächlich löschen Sie den Datensatz. (je nachdem, wie deine DB konfiguriert ist). Dies ist, was wir versuchen zu vermeiden, mit den soft-löschen-Muster zu beginnen!!!
Stieß ich auf ein interessanter blog post, der überschreibt die Standard - SaveChanges
Methode der DbContext
so, dass alle Einträge mit einem EntityState.Deleted
sind geändert zurück zu EntityState.Modified
aber das wieder, fühlt sich "hacky" und eher unsicher. Allerdings bin ich bereit, es auszuprobieren, wenn es löst die Probleme, ohne irgendwelche unbeabsichtigten Nebenwirkungen.
Also hier bin ich StackOverflow. Ich hab recherchiert, meine Optionen sehr ausführlich, wenn ich so sagen darf mich, und ich bin mit meinem Latein am Ende. So, jetzt Wende ich mich an Euch. Wie haben Sie die umgesetzt weichen löscht in Ihrem Unternehmen Anwendung?
Wiederholen, dies sind die Anforderungen, die ich bin auf der Suche nach:
- Abfragen sollten automatisch ausschließen, die
Deleted
Personen auf der DB-Ebene - Löschen einer Entität und der Aufruf 'SaveChanges' sollten einfach aktualisieren Sie den entsprechenden Datensatz und haben keine weiteren Nebenwirkungen.
- Wenn die Navigations-Eigenschaften geladen werden, egal ob lazy oder eager, der
Deleted
sollten diejenigen werden automatisch ausgeschlossen.
Ich freue mich über alle Vorschläge, danke im Voraus.
- Haben Sie versucht, Sie anzurufen .ToList() für das erste laden zu zwingen, EF, um in den Speicher laden?
- Ich fürchte, Ihr Kommentar macht nicht viel Sinn. Hast du wirklich gelesen, meine ganze Frage in unter 1 minute? Das ist beeindruckend!
- Danke für den Sarkasmus, wenn jemand versucht, Ihnen zu helfen.
- Meine Entschuldigung, ich meine nicht, Sie zu beleidigen. Aufruf von ToList auf dem Fitness-Studio-Mitglieder wäre in der Tat laden Sie die Fitness-Studio-Mitglieder in den Speicher, aber ich sehe wirklich nicht, wie, dass würde mein problem lösen?
- Ich denke, Sie müssen zurück zu den Grundlagen. Was Sie erklärt, ist nicht meine Erfahrung mit EF. "Aber, wenn ich Durchlaufen diese Fitness-Studio-Mitglieder, Ihre Trainingseinheiten werden aus der Datenbank geladen, ohne Rücksicht auf Ihr Gelöscht-flag"...... Es ist etwas falsch hier. Es MUß NICHT auch diese Aufzeichnungen. Überprüfen Sie Ihr Prädikat. 2. Deaktivieren LAZY Loading in Ihrem Kontext. Sobald Sie das haben, sortiert, explizit laden, und das Training für jedes Fitness-Mitglied in einer for-Schleife. Gebrauch, den er IQueryable zurück geben.
- Nun, natürlich muss es nicht enthalten die Datensätze, das ist, was ich versuche zu erreichen! Meine Frage ist: wie? Auch: deaktivieren der lazy loading an dieser Stelle, das ist nicht wirklich eine option, da unsere Anwendung bereits verwendet es königlich überall. Ich will auch nicht aufgeben, die Vorteile von lazy loading.
- Weich Löschen ist viel schwieriger als es scheinen mag auf den ersten Blick, EF oder nicht. Ich schlage vor, Sie sollten betrachten Sie andere Muster wie Archivierung zum Beispiel. Finden Sie diese stackoverflow.com/questions/9880623/soft-delete-vs-db-archive und auch das ayende.com/blog/4157/avoid-soft-deletes (es ist auf Nhibernate, aber die Idee ist die gleiche).
- Ich fürchte, die Entscheidung zur Nutzung der soft-löschen-Muster war nicht meins zu machen. Ich bin berufen zu lösen alle Ihre Probleme, obwohl. 🙂
Du musst angemeldet sein, um einen Kommentar abzugeben.
Nach viel Forschung, habe ich endlich einen Weg gefunden, das zu erreichen, was ich wollte.
Der Kern von ihm ist, dass ich abfangen materialisierte Personen mit einem event-handler am Objekt Kontext, und dann Spritzen meine benutzerdefinierten Auflistungsklasse in jeder Kollektion Eigenschaft, die ich finden kann (mit Reflexion).
Ist der wichtigste Teil abfangen der "DbCollectionEntry", die Klasse für die Beladung verantwortlich für die damit verbundene Sammlung von Eigenschaften. Durch wackeln mich zwischen die Entität und die DbCollectionEntry ich die volle Kontrolle über das, was geladen ist, Wann und wie. Der einzige Nachteil ist, dass diese DbCollectionEntry Klasse hat wenig bis gar keine öffentlichen Mitglieder, die verlangt von mir, reflektion verwenden, um es zu manipulieren.
Hier ist meine eigene collection-Klasse, die ICollection implementiert und enthält einen Verweis auf die entsprechende DbCollectionEntry:
Wenn Sie Blättern Sie durch es, werden Sie feststellen, dass der wichtigste Teil ist die "Einheiten" - Eigenschaft, die wird lazy load werden die tatsächlichen Werte. Im Konstruktor der FilteredCollection ich passiere ein optionales ICollection für das Szenario ist, wo die Sammlung wird bereits mit Spannung geladen.
Natürlich müssen wir noch konfigurieren, Entity Framework, so dass unsere FilteredCollection wird überall dort eingesetzt, wo es der Sammlung-Eigenschaften. Dies kann erreicht werden durch Einhaken in die ObjectMaterialized Veranstaltung von der zugrunde liegenden ObjectContext von Entity Framework:
Es sieht alles ziemlich kompliziert, aber was er im Grunde macht, ist, Scannen Sie die materialisierte geben für die Sammlung von Eigenschaften, und ändern Sie den Wert einer gefilterten Sammlung. Es geht auch bei der DbCollectionEntry, um die gefilterte Auflistung, so kann es die Arbeit seiner Magie.
Diese umfasst das gesamte "be-Organisationen" Teil. Der einzige Nachteil bisher ist, dass Spannung geladen Kollektion Eigenschaften ist immer noch die gelöschten Entitäten, sondern Sie sind gefiltert in der " Add " - Methode des FilterCollection-Klasse. Dies ist ein akzeptabler Nachteil, obwohl, ich habe noch zu tun einige Tests auf, wie sich dies auf die SaveChanges () - Methode.
Natürlich, dies lässt allerdings noch ein Problem: es gibt keine automatische Filterung, die auf die Abfragen. Wenn Sie möchten, Holen Sie sich das Fitness-Studio-Mitglieder, die ein Training in der vergangenen Woche, die Sie ausschließen möchten, die gelöscht workouts automatisch.
Erreicht wird dies durch eine ExpressionVisitor, dass gilt automatisch eine '.Where(e => !e."Gelöscht")' filter auf jeden IQueryable, die es finden kann in einem gegebenen Ausdruck.
Hier ist der code:
Ich bin vielleicht ein bisschen kurz auf Zeit, also werde ich wieder zu diesem Beitrag später mit mehr details, aber das wesentliche ist geschrieben und für diejenigen von Euch, begierig, alles zu versuchen; ich gebucht habe, das komplette test-Anwendung hier: https://github.com/amoerie/TestingGround
Allerdings könnte es noch einige Fehler, da dies sehr viel ein work in progress. Die konzeptionelle Idee ist der Ton aber, und ich erwarte, dass es voll funktionsfähig, bald habe ich einmal umgestaltet werden, dass alles ordentlich und die Zeit finden, zu schreiben, einige tests für diese.
Einer evtl. Weg sein könnte, mit Spezifikationen, die mit einer Basis-Spezifikation, prüft die soft gelöscht-flag für alle Abfragen zusammen mit einer include-Strategie.
Werde ich veranschaulichen, eine angepasste version der Spezifikation Muster, das ich verwendet habe in einem Projekt (das hatte seinen Ursprung in diesem blog-post)
Den IPredicateBuilder ist ein wrapper zu dem Prädikat builder enthalten, die in der LINQKit.dll.
Die Spezifikation Basis-Klasse dafür verantwortlich ist, zu erstellen, die das Prädikat builder. Einmal erstellt die Kriterien, die angewendet werden sollte, um alle query Hinzugefügt werden können. Das Prädikat builder kann dann übergeben werden, die die ererbten Spezifikationen für das hinzufügen weiterer Kriterien. Zum Beispiel:
Den IdSpecification das volle Prädikat wäre dann:
Die Spezifikation kann dann übergeben werden, um das repository und die Nutzung des
PredicateBuilder
Eigenschaft der Aufbau der where-Klausel:AsExpandable()
ist Teil der LINQKit.dll.In Bezug auf die inklusive/lazy loading-Eigenschaften kann man verlängern die Spezifikation mit einer weiteren Eigenschaft zu gehören. Die Spezifikation sockel hinzufügen können, die die base enthält und dann Kind-Spezifikationen hinzufügen Ihre zählen. Das repository kann dann vor dem abrufen aus der db gelten die gehören von der Spezifikation.
Lassen Sie mich wissen, wenn etwas unklar ist. Ich habe versucht, nicht zu machen dies zu einem monster post so einige details könnten weggelassen werden.
Edit: ich erkannte, dass ich nicht vollständig beantworten Ihre Frage(N); navigation Eigenschaften. Was ist, wenn Sie die navigation Eigenschaft intern (mit dieser post um es zu konfigurieren und erstellen von nicht-zugeordneten öffentlichen Eigenschaften, die IQueryable. Die nicht zugeordneten Eigenschaften können ein benutzerdefiniertes Attribut und das repository fügt die Basis-Spezifikation Prädikat der where -, ohne sehnsüchtig zu laden. Wenn jemand gelten ein eifriger Betrieb wird der filter angewendet. So etwas wie:
Habe ich noch nicht getestet den obigen code, aber es könnte funktionieren mit einigen Optimierungen 🙂
Edit 2: Löscht.
Wenn Sie mit einer Allgemeinen/generischen repository können Sie einfach fügen Sie einige weitere Funktionen, um die delete-Methode:
Haben Sie als Ansichten verwenden, die in Ihrer Datenbank zu laden, Ihr problem zu Personen mit den gelöschten items ausgeschlossen?
Es bedeutet, müssen Sie gespeicherte Prozeduren verwenden, um anzeigen
INSERT
/UPDATE
/DELETE
Funktionalität, aber es wäre auf jeden Fall dein problem lösen, wennWorkout
Karten zu einer Ansicht mit den gelöschten Zeilen weggelassen. Auch dies kann nicht die gleiche Arbeit in einem code-first-Ansatz...