Powiązane pole, czy odpytywanie bazy za kazdym zapytaniem - brak lazyloading

0

Sorki za nazwę tematu ;)
Rzecz ma się tak

mam klasę User, która ma wiele powiązań, np:

Messages,
UserDetails,
Interests,
CokolwiekInnego,
Friends

oraz wiele właściwości prostych

używam EF Core 2.0 gdzie lazyloading jeszcze nie istnieje, ale w dao mam możliwość dołączania odpowiednich tabel.
np. w przypadku gdy będą mniej interesować UserDetails, mogę w UsersDao stworzyc metodę

QueryUserWithDetals(..)
{
 _dbContext.Users.Include(u=>u.UserDetails)
}

w przypadku messages, adekwatnie:

QueryUsersWithMessages()
{
 _dbContext.Users.Include(u=>u.Messages)
}

w przypadku chęci wykorzystania wielu dowiązań można naraz pobrać i users i messages (ale to akurat bezsensowy przykład)

Tworzę Web-Api, więc to klient w zakłądce wiadomości będzie pośrednio wołał o QueryUsersWithMessages, zaś wyszukiwarka użytkowników będzie pośrednio wołała do QueryUserWithDetals

zwracane tak czy siak jest zawsze jakieś DTO (np. usersDto będzie miało już tablicę [FriendsDto]) - ale uzupełnianie danych w tym dto będzie zawsze zależało od kontekstu - znowu - w wiadomościach użytkownika nie będą mnie interesowały szczegółowe informacje dot. użytkowinka, jak np. numer buta

A więc... czy jest w ogóle sens w klasie User dać Messages, UserDetails, itd.?
Czy nie lepiej to bardziej rozdzielić na serwisy:
UsersDetailsService, UsersService, MessagesService, FriendsService

a API tak czy siak będzie wołało odpowiedni serwis, np. w zapytaniu o "znajomych" zapyta FriendsService,
a ten z kolei zada pytanie do bazy:

UsersFriends.Where(x=> x.user1Id == queryId || x.user2Id == queryId) 

Więc tu nachodzi mnie pytanie : po co w ogóle na poziomie klasy USER dawać "odnośniki" do innych powiązanych obiektów,
skoro zawsze można odpytać konkretną tabelę

Jedynym atutem posiadania w klasie User pola Messages jest odwołanie się do wiadomości Usera przez userEntity.Messages
Troszkę zagmatwałem, ale w sumie robić tak, że User zawiera Messages, Details, itd, czy starać się je izolować (bo co mnie obchodzą Messages usera, kiedy jestem na poziomie Users.Friends)

Od czego to zależy?
Podejrzewam że w aplikacjach okienkowych, czy raczej tam, gdzie cały czas jest zapewniony dostęp do bazy (czyli np. zmieniamy nazwisko usera, a po opuszczeniu textboxa to się od razu aktualizuje) wykorzysta się raczej user.Details, user.Friends, a zaś w WEBie, a przede wszystkim w Web api, gdzie klient za każdym razem odpytuje api (za każdym przekliknięciem zakładki) lepiej jest mieć oddzielnie te klasy, niepowiązane z sobą?

0

Jak to nie ma lazy loading to jak ten ORM działa?

Jeśli twoja apka robi tylko zapis i odczyt z bazy to wystarczyłby ci jeden serwis oparty na refleksjach, bez odkrywania iqueryable. Pizza MVC działa w podobny sposób.

0
._. napisał(a):

Jak to nie ma lazy loading to jak ten ORM działa?

Jeśli twoja apka robi tylko zapis i odczyt z bazy to wystarczyłby ci jeden serwis oparty na refleksjach, bez odkrywania iqueryable. Pizza MVC działa w podobny sposób.

https://docs.microsoft.com/pl-pl/ef/core/what-is-new/ef-core-2.1

0

Jeśli zawsze zwracasz DTO, to może warto używać projekcji? Wtedy możesz np. zrobić coś takiego:

var userDtoList = return _dbContext.Users
    .Select(x => new JakiśDTO {
        Name = x.Name,
        LastMessages = x.Messages.OrderByDescending(m => m.Date).Take(10).ToList(),
        MessageCount = user.Messages.Count()
    })
    .ToList();

Jeśli rzutujesz dane z IQueryable na swój typ DTO, to EF automatycznie generuje zapytanie (zawsze jedno w przypadku EF6, jedno lub więcej w EF Core), które sięga do odpowiednich tabel. Nie musisz wtedy zastanawiać się, jakie Includy dodać, a dzięki navigation properties możesz wygodnie sięgać do powiązanych tabel bez ręcznego pisania joinów. Natomiast przy zapisywaniu możesz używać navigation properties albo osobnych tabel, jak Ci wygodniej w danym przypadku.

Powyższe rozwiązanie niekoniecznie może być jednak wydajne. W EF6 założenie, że wszystko musi pójść w 1 zapytaniu powoduje czasem generowanie horrendalnego SQLa (w pracy zdarzały się nam zapytania po kilka MB, jeśli zrobiliśmy niedbałe, skomplikowane zapytania), a także prowadzi do duplikacji danych (jeśli masz obiekt, a w nim listę 100 pozycji, to właściwości obiektu będą powtórzone 100 razy, bo tak działa join). Z kolei EF Core rozdziela zapytania LINQ na kilka zapytań SQL, co czasem z kolei prowadzi do problemu select N+1 (ostatnio jak sprawdzałem na 2.1, to przypadek typu Obiekt.Include(x => x.Lista).Theninclude(x => x.KolejnaZagnieżdżonaLista) już powodował N+1).

Podsumowując, do wszystkich rozwiązań trzeba podchodzić z ostrożnością i obserwować co się dzieje pod spodem :)

0

mad penguin, no wlasnie chodzi mi mniej więcej o to:

czy lepiej już po stronie encji (tych klas które są bezpośrendio mapowane do bazy) tworzyć obiekty np. User.Messages i mapując do dto robiąc mniej więcej to co ty,
czy możę lepiej robic sobie pierwsze zapytanie: dao<User>.Get(id), a następnie:
user.Messages = Dao<messages>.Query(...).MapToDto(..);

czyli po stronie kodu na poziomie encji nie jest wiadomo, że ma one jakieś wiadomości, detailsy, czy inne bzdety, natomiast ta wiedza byłaby na poziomie logiki biznesowej (czyli serwisu pobierające osobno usera i wiadomości dla niego)

0

Zmień z normalizowany model na z denormalizowany i zapis z triger'ami, które będą aktualizować ten model. Albo zrób sobie zmaterializowany widok (czy tam perspektywę). Lub w ogóle zmień bazę na NoSQL i problem rozwiązany. Bez join'ów też można, żyć, lecz nie jest to łatwe.

1

Nie tworząc właściwości w code first i trzymając wszystkie związki w logice biznesowej, pozbawiasz się chociażby constraintów na bazie, a więc zmniejszasz bezpieczeństwo spójności danych. Poza tym utrudniasz późniejsze utrzymanie takiego projektu sprawiając, że zależności są mniej widoczne. Zatem nie warto ich unikać.
Natomiast co do ładowania danych to jak powiedziałem nie ma jednej odpowiedzi. Najlepiej robić tak, jak wygodnie, a jeśli okaże się, że jakieś zapytanie jest niewydajne to dopiero wtedy optymalizować.

1 użytkowników online, w tym zalogowanych: 0, gości: 1