Filtruj wszystkie właściwości nawigacji, zanim zostaną załadowane (leniwe lub chętne) do pamięci

Dla przyszłych użytkowników: dla EF6 prawdopodobnie lepiej będzie korzystać z filtrów, na przykład za pośrednictwem tego projektu:https://github.com/jbogard/EntityFramework.Filters

W aplikacji, którą budujemy, stosujemy wzorzec „miękkie usuwanie”, w którym każda klasa ma bool „Usunięte”. W praktyce każda klasa dziedziczy po tej klasie bazowej:

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

    public virtual bool Deleted { get; set; }
}

Aby podać krótki przykład, załóżmy, że mam zajęciaGymMember iWorkout:

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

Kiedy ściągam listę członków siłowni z bazy danych, mogę się upewnić, że żaden z „usuniętych” członków siłowni nie został pobrany, tak:

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

Jednak, kiedy powtarzam przez tych członków siłowni, ichWorkouts są ładowane z bazy danych bez względu na ichDeleted flaga. Chociaż nie mogę obwiniać Entity Framework za to, że nie wychwytuję tego, chciałbym skonfigurować lub przechwycić leniwe ładowanie właściwości, aby usunięte właściwości nawigacyjne nigdy nie zostały załadowane.

Przeglądałem moje opcje, ale wydają się rzadkie:

ZamierzamDatabase First i używaj odwzorowania warunkowego dla każdego obiektu dla każdej właściwości jeden-do-wielu.

To po prostu nie jest opcja, ponieważ byłoby zbyt wiele pracy ręcznej. (Nasza aplikacja jest ogromna i każdego dnia staje się coraz bardziej przytulna). Nie chcemy również rezygnować z zalet korzystania z Kodu Pierwszy (którego jest wiele)

Zawsze chętnie ładuję właściwości nawigacyjne.

Ponownie, nie ma opcji. Ta konfiguracja jest dostępna tylko dla jednego podmiotu. Zawsze chętnie ładujące podmioty nakładałyby również poważną karę za wykonanie.

Stosowanie wzoru Expression Visitor, który automatycznie wstrzykuje.Where(e => !e.Deleted) gdziekolwiek znajdzieIQueryable<Entity>, jak w opisietutaj itutaj.

Przetestowałem to w aplikacji do sprawdzania koncepcji i działało cudownie. To była bardzo interesująca opcja, ale niestety nie stosuje filtrowania do ładnie załadowanych właściwości nawigacji. Jest to oczywiste, ponieważ te leniwe właściwości nie pojawiają się w wyrażeniu / zapytaniu i jako takie nie mogą być zastąpione. Zastanawiam się, czy Entity Framework pozwoli na punkt wtrysku gdzieś w ichDynamicProxy klasa, która ładuje leniwe właściwości. Obawiam się również o inne konsekwencje, takie jak możliwość złamaniaInclude mechanizm w EF.

Pisanie niestandardowej klasy, która implementuje ICollection, ale filtrujeDeleted encje automatycznie.

To było właściwie moje pierwsze podejście. Pomysł polegałby na użyciu właściwości kopii dla każdej właściwości kolekcji, która wewnętrznie używa niestandardowej klasy kolekcji:

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

}

Chociaż takie podejście nie jest w rzeczywistości złe, nadal mam z nim pewne problemy:

Nadal ładuje wszystkieWorkouts do pamięci i filtrujeDeleted te, gdy trafi ustawiacz nieruchomości. Moim skromnym zdaniem to już za późno.

Istnieje logiczna niezgodność między wykonywanymi zapytaniami a ładowanymi danymi.

Obraz scenariusz, w którym chcę listę członków siłowni, którzy wykonali trening od zeszłego tygodnia:

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

To zapytanie może zwrócić element sali gimnastycznej, który ma tylko treningi, które są usunięte, ale także spełniają predykat. Gdy zostaną załadowane do pamięci, wygląda na to, że ten członek siłowni w ogóle nie ma treningów! Można powiedzieć, że programista powinien być świadomy tegoDeleted i zawsze włączaj to do swoich zapytań, ale tego chciałbym naprawdę uniknąć. Może ExpressionVisitor może zaoferować odpowiedź tutaj ponownie.

Faktycznie niemożliwe jest oznaczenie właściwości nawigacji jakoDeleted podczas korzystania z CustomCollection.

Wyobraź sobie ten scenariusz:

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

Można oczekiwać, że odpowiednieWorkout rekord jest aktualizowany w bazie danych, a mylisz się! OdgymMember jest kontrolowany przezChangeTracker za wszelkie zmiany nieruchomośćgymMember.Workouts nagle zwróci 1 mniej treningu. To dlatego, że CustomCollection automatycznie filtruje usunięte instancje, pamiętasz? Teraz Entity Framework uważa, że ​​trening musi zostać usunięty, a EF spróbuje ustawić FK na null lub faktycznie usunąć rekord. (w zależności od konfiguracji bazy danych). Właśnie tego staraliśmy się uniknąć dzięki delikatnemu wzorowi kasowania !!!

Natknąłem się naciekawy blog post, który zastępuje domyślneSaveChanges metodaDbContext tak, aby wszelkie wpisy zEntityState.Deleted są zmienione z powrotem naEntityState.Modified ale to znowu wydaje się „hacky” i raczej niebezpieczne. Jestem jednak skłonny wypróbować, jeśli rozwiązuje problemy bez niezamierzonych efektów ubocznych.

Więc tutaj jestem StackOverflow. Wyszukałem dość obszernie moje opcje, jeśli mogę tak powiedzieć, i jestem na wyczerpaniu. Więc teraz zwracam się do ciebie. W jaki sposób zaimplementowałeś miękkie usuwanie w aplikacji korporacyjnej?

Powtarzam, są to wymagania, których szukam:

Zapytania powinny automatycznie wykluczaćDeleted encje na poziomie DBUsunięcie jednostki i wywołanie „SaveChanges” powinno po prostu zaktualizować odpowiedni rekord i nie mieć innych skutków ubocznych.Po załadowaniu właściwości nawigacyjnych, zarówno leniwych, jak i chętnych,Deleted te powinny być automatycznie wykluczone.

Z niecierpliwością czekam na wszelkie sugestie, z góry dziękuję.

questionAnswers(3)

yourAnswerToTheQuestion