Pytanie EF: Dołącz do klauzuli where


Jak sugeruje tytuł, szukam sposobu na wykonanie klauzuli where w połączeniu z include.

Oto moje sytuacje: Odpowiadam za obsługę dużej aplikacji pełnej zapachów kodu. Zmiana zbyt wielu kodów powoduje wszędzie błędy, więc szukam najbezpieczniejszego rozwiązania.

Powiedzmy, że mam obiekt Bus i obiekt People (Bus ma zbiór nawigacyjny Collection of People). W mojej kwerendzie muszę wybrać wszystkie autobusy z tylko obudzonymi pasażerami. Jest to uproszczony fałszywy przykład

W bieżącym kodzie:

var busses = Context.Busses.Where(b=>b.IsDriving == true);
foreach(var bus in busses)
{
   var passengers = Context.People.Where(p=>p.BusId == bus.Id && p.Awake == true);
   foreach(var person in passengers)
   {
       bus.Passengers.Add(person);
   }
}

Po tym kodzie kontekst jest usuwany, a w metodzie wywołania uzyskane jednostki Bus są odwzorowywane na klasę DTO (100% kopia Entity).

Ten kod wywołuje wiele wywołań do DB, który jest No-Go, więc znalazłem to rozwiązanie NA blogach MSDN

To zadziałało dobrze podczas debugowania wyniku, ale kiedy obiekty są odwzorowane na DTO (przy użyciu AutoMapper) otrzymuję wyjątek, że Kontekst / Połączenie zostało zamknięte i że obiekt nie może zostać załadowany. (Kontekst jest zawsze zamknięty, nie można tego zmienić :()

Muszę więc upewnić się, że wybrani pasażerowie są już załadowani (IsLoaded na właściwości nawigacji również jest fałszywe). Jeśli przejrzę kolekcję Pasażerów, The Count również zgłasza wyjątek, ale istnieje również kolekcja na Collection of Passegers, zwana "opakowanymi jednostkami powiązanymi", która zawiera moje filtrowane obiekty.

Czy istnieje sposób na załadowanie owiniętych powiązanych obiektów do całej kolekcji? (Nie mogę zmienić konfiguracji odwzorowania automatu, ponieważ jest ona używana w całej aplikacji).

Czy istnieje inny sposób na uzyskanie aktywnych pasażerów?

Wszelkie wskazówki są mile widziane ...

Edytować

Odpowiedź Gert Arnold nie działa, ponieważ dane nie są ładowane z zapałem. Ale kiedy upraszczam i usuwam miejsce, w którym jest załadowany. Jest to bardzo dziwne, ponieważ sql execute zwraca wszystkich pasażerów w obu przypadkach. Tak więc musi być problem z umieszczeniem wyników z powrotem w jednostce.

Context.Configuration.LazyLoadingEnabled = false;
var buses = Context.Busses.Where(b => b.IsDriving)
        .Select(b => new 
                     { 
                         b,
                         Passengers = b.Passengers
                     })
        .ToList()
        .Select(x => x.b)
        .ToList();

Edytuj2

Po wielu zmaganiach odpowiedź Gert Arnolda działa! Jak Gert Arnold zasugerował, że musisz wyłączyć Lazy Loading i trzymać go w pozycji OFF. Spowoduje to wprowadzenie dodatkowych zmian w aplikacji, ponieważ poprzedni programista pokochał Lazy Loading -_-


29
2018-05-28 18:18


pochodzenie


jest to tylko przykład napisany w stackoveflow bez intellisense: p Naprawiono teraz - Beejee
Czy możesz również pokazać nam, jak będzie wyglądać przykład odpowiednich części implementacji klas dla autobusów, osób i pasażerów (takie jak klucze obce i właściwości nawigacji)? - Travis J
Pasażerowie to propozycja nawigacji tak - Beejee
Jestem nieco zaskoczony, że to pytanie prawie nie zwraca uwagi, biorąc pod uwagę, jak trudno mi było znaleźć i jak to jest świetny sposób, aby ograniczyć ilość danych EF zapytań do bazy danych. Czy ludzie nie widzieli zapytań, które EF tworzy dla bazy danych? - Ellesedil


Odpowiedzi:


Możesz zapytać o wymagane obiekty według

Context.Configuration.LazyLoadingEnabled = false;
// Or: Context.Configuration.ProxyCreationEnabled = false;
var buses = Context.Busses.Where(b => b.IsDriving)
            .Select(b => new 
                         { 
                             b,
                             Passengers = b.Passengers
                                           .Where(p => p.Awake)
                         })
            .AsEnumerable()
            .Select(x => x.b)
            .ToList();

To, co dzieje się tutaj, polega na tym, że najpierw wyszukujesz autobusy i budzisz pasażerów z bazy danych. Następnie, AsEnumerable() przełącza z LINQ na Entities na LINQ na obiekty, co oznacza, że ​​autobusy i pasażerowie zostaną zmaterializowani, a następnie przetworzeni w pamięci. Jest to ważne, ponieważ bez niego EF zmaterializuje tylko końcową projekcję, Select(x => x.b)nie pasażerowie.

Teraz EF ma tę funkcję rozwiązywanie relacji który zajmuje się ustawianiem wszystkich powiązań między obiektami zmaterializowanymi w kontekście. Oznacza to, że dla każdego Bus teraz ładują się tylko jego wybudzeni pasażerowie.

Kiedy otrzymasz kolekcję autobusów przez ToListmasz autobusy z pasażerami, których chcesz i możesz je zmapować za pomocą AutoMappera.

Działa to tylko w przypadku, gdy leniwy ładowanie jest wyłączone. W przeciwnym razie EF będzie leniwie ładować wszystko pasażerowie dla każdego autobusu, gdy pasażerowie mają dostęp podczas konwersji na DTO.

Istnieją dwa sposoby na wyłączenie leniwego ładowania. Wyłączanie LazyLoadingEnabled ponownie aktywuje opóźnione ładowanie po ponownym włączeniu. Wyłączanie ProxyCreationEnabled utworzy elementy, które nie są zdolne do leniwego ładowania sami, więc nie zaczną leniwego ładowania po ProxyCreationEnabled jest ponownie włączony. To może być najlepszy wybór, gdy kontekst żyje dłużej niż tylko jedno zapytanie.

Ale ... wiele do wielu

Jak już wspomniano, ta praca polega na rozwiązaniu relacji. Jednak, jak wyjaśniono tutaj przez Slauma, fixing relacji nie działa ze związkami wiele do wielu. Gdyby Bus-Passenger jest wiele-do-wielu, jedyne co możesz zrobić, to naprawić samodzielnie:

Context.Configuration.LazyLoadingEnabled = false;
// Or: Context.Configuration.ProxyCreationEnabled = false;
var bTemp = Context.Busses.Where(b => b.IsDriving)
            .Select(b => new 
                         { 
                             b,
                             Passengers = b.Passengers
                                           .Where(p => p.Awake)
                         })
            .ToList();
foreach(x in bTemp)
{
    x.b.Pasengers = x.Passengers;
}
var busses = bTemp.Select(x => x.b).ToList();

... a całość staje się jeszcze mniej atrakcyjna.

Narzędzia innych firm

Jest biblioteka, EntityFramework.DynamicFilters to sprawia, że ​​jest to o wiele łatwiejsze. Umożliwia definiowanie globalnych filtrów dla jednostek, które będą następnie stosowane za każdym razem, gdy jednostka jest pytana. W twoim przypadku może to wyglądać następująco:

modelBuilder.Filter("Awake", (Person p) => p.Awake, true);

Teraz, jeśli masz ...

Context.Busses.Where(b => b.IsDriving)
       .Include(b => b.People)

... zobaczysz, że filtr zostanie zastosowany do dołączonej kolekcji.

Możesz także włączyć / wyłączyć filtry, aby mieć kontrolę nad tym, kiedy zostaną zastosowane. Myślę, że jest to bardzo schludna biblioteka.

Istnieje podobna biblioteka od twórcy AutoMapper: EntityFramework.Filters

Rdzeń Entity Framework

Od wersji 2.0.0, EF-core ma Filtry zapytań na poziomie modelu. Chociaż jest to znakomite uzupełnienie jego funkcji, jak na razie ograniczenie polega na tym, że nie można go zastosować do właściwości nawigacji, tylko do głównej encji zapytania. Mamy nadzieję, że w późniejszej wersji filtry te uzyskają szersze zastosowanie.


40
2018-05-28 20:46



Na mój brakujący komentarz dotyczący EntityFrameworkDynamicFilters niestety nie jest opcją dla DB First: github.com/zzzprojects/EntityFramework.DynamicFilters/issues/12 - xr280xr


Zrzeczenie się: Jestem właścicielem projektu Entity Framework Plus

Funkcja EF + Query IncludeFilter umożliwia filtrowanie powiązanych obiektów.

var buses = Context.Busses
                   .Where(b => b.IsDriving)
                   .IncludeFilter(x => x.Passengers.Where(p => p.Awake))
                   .ToList();

Wiki: EF + Query IncludeFilter


16
2018-03-14 12:05