Filtern Sie alle Navigationseigenschaften, bevor sie (träge oder eifrig) in den Speicher geladen werden

Für zukünftige Besucher: Für EF6 ist es wahrscheinlich besser, Filter zu verwenden, zum Beispiel über dieses Projekt:https://github.com/jbogard/EntityFramework.Filters

In der Anwendung, die wir erstellen, wenden wir das "soft delete" -Muster an, bei dem jede Klasse einen "Deleted" -Bool hat. In der Praxis erbt jede Klasse einfach von dieser Basisklasse:

public abstract class Entity
{
    public virtual int Id { get; set; }

    public virtual bool Deleted { get; set; }
}

Um ein kurzes Beispiel zu geben: Angenommen, ich habe die KlassenGymMember undWorkout:

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 die Liste der Fitnessstudio-Mitglieder aus der Datenbank abrufe, kann ich sicherstellen, dass keines der gelöschten Fitnessstudio-Mitglieder wie folgt abgerufen wird:

var gymMembers = context.GymMembers.Where(g => !g.Deleted);

Jedoch, wenn ich durch diese Turnhallenmitglieder iteriere, ihreWorkouts aus der Datenbank geladen werden, ohne Rücksicht auf ihreDeleted Flagge. Obwohl ich Entity Framework nicht dafür verantwortlich machen kann, dass ich dies nicht aufgegriffen habe, möchte ich das Laden von verzögerten Eigenschaften so konfigurieren oder abfangen, dass gelöschte Navigationseigenschaften niemals geladen werden.

Ich habe meine Optionen durchgesehen, aber sie scheinen selten zu sein:

Ich gehe zuDatabase First und verwenden Sie die bedingte Zuordnung für jedes Objekt für jede Eins-zu-Viele-Eigenschaft.

Dies ist einfach keine Option, da es zu viel Handarbeit wäre. (Unsere Bewerbung ist riesig und wird von Tag zu Tag größer.) Wir möchten auch nicht auf die Vorteile der Verwendung von Code First verzichten (von denen es viele gibt)

Navigationseigenschaften immer eifrig laden.

Wieder keine Option. Diese Konfiguration ist nur pro Entität verfügbar. Das eifrige Laden von Entitäten würde ebenfalls eine ernsthafte Leistungsstrafe nach sich ziehen.

Anwenden des Expression Visitor-Musters, das automatisch injiziert wird.Where(e => !e.Deleted) überall findet es eineIQueryable<Entity>, wie beschriebenHier undHier.

Ich habe dies tatsächlich in einer Proof-of-Concept-Anwendung getestet, und es hat wunderbar funktioniert. Dies war eine sehr interessante Option, aber leider kann keine Filterung auf träge geladene Navigationseigenschaften angewendet werden. Dies liegt auf der Hand, da diese faulen Eigenschaften im Ausdruck / in der Abfrage nicht erscheinen und daher nicht ersetzt werden können. Ich frage mich, ob Entity Framework irgendwo in ihrer Umgebung einen Einspeisepunkt zulassen würdeDynamicProxy Klasse, die die Lazy-Eigenschaften lädt. Ich fürchte auch um andere Konsequenzen, wie die Möglichkeit, das zu brechenInclude Mechanismus in EF.

Schreiben einer benutzerdefinierten Klasse, die ICollection implementiert, jedoch die Klasse filtertDeleted Entitäten automatisch.

Dies war eigentlich mein erster Ansatz. Die Idee wäre, eine Backing-Eigenschaft für jede Auflistungseigenschaft zu verwenden, die intern eine benutzerdefinierte Collection-Klasse verwendet:

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

}

Während dieser Ansatz eigentlich nicht schlecht ist, habe ich noch einige Probleme damit:

Es lädt immer noch alleWorkouts in den Speicher und filtert dieDeleted diejenigen, wenn der Property Setter getroffen wird. Meiner bescheidenen Meinung nach ist dies viel zu spät.

Es besteht eine logische Abweichung zwischen den ausgeführten Abfragen und den geladenen Daten.

Stellen Sie sich ein Szenario vor, in dem ich eine Liste der Fitnessstudio-Mitglieder sehen möchte, die seit letzter Woche trainiert haben:

var gymMembers = context.GymMembers.Where(g => g.Workouts.Any(w => w.Date >= DateTime.Now.AddDays(-7).Date));

Diese Abfrage gibt möglicherweise ein Fitnessstudio-Mitglied zurück, dessen Trainingseinheiten nur gelöscht wurden, das Prädikat jedoch erfüllt. Sobald sie in den Speicher geladen sind, sieht es so aus, als ob dieses Mitglied überhaupt keine Trainingseinheiten hat! Man könnte sagen, der Entwickler sollte sich des Problems bewusst seinDeleted und nimm es immer in seine Abfragen auf, aber das möchte ich wirklich vermeiden. Vielleicht könnte der ExpressionVisitor die Antwort hier noch einmal anbieten.

Es ist tatsächlich unmöglich, eine Navigationseigenschaft als zu markierenDeleted bei Verwendung der CustomCollection.

Stellen Sie sich dieses Szenario vor:

var gymMember = context.GymMembers.First();
gymMember.Workouts.First().Deleted = true;
context.SaveChanges();`

Man würde das angemessen erwartenWorkout Datensatz ist in der Datenbank aktualisiert, und Sie würden sich irren! Seit dergymMember wird geprüft von derChangeTracker für eventuelle änderungen die eigenschaftgymMember.Workouts wird plötzlich 1 weniger Training zurück. Das liegt daran, dass CustomCollection gelöschte Instanzen automatisch filtert. Jetzt ist Entity Framework der Meinung, dass das Training gelöscht werden muss, und EF wird versuchen, die FK auf null zu setzen oder den Datensatz tatsächlich zu löschen. (abhängig davon, wie Ihre DB konfiguriert ist). Dies wollten wir mit dem Soft-Delete-Pattern zunächst vermeiden !!!

Ich stolperte über eineinteressanter Blog Beitrag, der die Standardeinstellung überschreibtSaveChanges Methode derDbContext damit alle Einträge mit einemEntityState.Deleted werden zurück zu geändertEntityState.Modified aber das fühlt sich wieder "hacky" und ziemlich unsicher an. Ich bin jedoch bereit, es auszuprobieren, wenn es Probleme ohne unbeabsichtigte Nebenwirkungen löst.

Hier bin ich also StackOverflow. Ich habe meine Optionen ziemlich ausführlich recherchiert, wenn ich so sagen darf, und ich bin am Ende meines Verstandes. Jetzt wende ich mich an dich. Wie haben Sie weiche Löschvorgänge in Ihrer Unternehmensanwendung implementiert?

Um es noch einmal zu wiederholen, sind dies die Anforderungen, nach denen ich suche:

Abfragen sollten das automatisch ausschließenDeleted Entitäten auf DB-EbeneDas Löschen einer Entität und der Aufruf von 'SaveChanges' sollte einfach den entsprechenden Datensatz aktualisieren und keine weiteren Nebenwirkungen haben.Wenn Navigationseigenschaften geladen werden, egal ob faul oder eifrig, wird dieDeleted diejenigen sollten automatisch ausgeschlossen werden.

Ich freue mich auf alle Vorschläge, vielen Dank im Voraus.

Antworten auf die Frage(3)

Ihre Antwort auf die Frage