Virtualna właściwość nawigacyjna w Entity

0

witam
Mam do was pewne pytanie, otóż przeglądam pewien kurs i zastanawiam się nad pewnym połączeniem dwóch kolumn ze sobą:

public class Album
    {
        public int AlbumId { get; set; }
        public int GenreId { get; set; }
        public string AlbumTitle { get; set; }
        public string ArtistName { get; set; }
        public DateTime DateAdded { get; set; }
        public string CoverFileName { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }
        public bool IsBestseller { get; set; }

        public virtual Genre Genre { get; set; }
        public bool IsHidden { get; set; }
    } 
public class Genre
    {
        public int GenreId { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public string IconFilename { get; set; }

        public ICollection<Album> Albums { get; set; }
    } 

I moje pytanie: Czemu

 public ICollection<Album> Albums { get; set; }

jako właściwość nawigacyjna nie jest ona oznaczona jako virtual gdyż autor raz oznacza ją jako virualną raz nie i właśnie nie wiem dlaczego i od czego to zależy?

2

Dodanie virtual powoduje:

  • Lazy Loading czyli załadowanie danych z bazy danych dopiero w momencie kiedy są potrzebne, na żądanie.
  • Efektywniejsze śledzenie zmian w entities(bytach?).
0

czyli zastosowanie

 virtual 

ma same plusy więc czemu autor kursu pisał raz tak raz tak?

2

Bo to nie są same plusy. Tego się używa wtedy, gdy jest potrzebne, a nie wszędzie.

0

a czy możesz mi wyjaśnić te lazy loading no bo "załadowanie danych w momencie kiedy są one potrzebne" - czyli zawsze chyba (najlepiej na jakimś przykładzie jakbys mógł)

4

Masz np. obiekt Faktura, która ma właściwość Klient klasy Klient. I teraz:

  1. Jeśli nie będziesz korzystał z lazy loading, to jeśli pobierzesz z kontekstu EF obiekt Faktura, z bazy od razu wczytany zostanie także obiekt Klient.
  2. Jeśli będziesz korzystał z lazy loading, to pobierając Fakturę pobierzesz tylko ją. Ale jeśli gdzieś w kodzie napiszesz np.: string nip = faktura.Klient.Nip, to wówczas dopiero z bazy zostanie doczytany Klient.
0

dzięki wielkie somekind już łapie :) i poza tym nie ma innych różnic ?

2

To jest ogromna różnica
W jednym przypadku jeśli chcesz przelecieć po wszystkich fakturach i zrobić coś na kliencie, poleci do bazy tysiące zapytań i ogólnie aplikacji będzie słabo działać
W drugim przypadku jeśli chcesz przelecieć po wszystkich fakturach ale nie robić niczego z klientami to klienci niepotrzebnie się pobrały i być może wydłużyły niepotrzebnie pobieranie listy faktur oraz zwiększyły liczbę przesyłanych danych z bazy

1
somekind napisał(a):

Masz np. obiekt Faktura, która ma właściwość Klient klasy Klient. I teraz:

  1. Jeśli nie będziesz korzystał z lazy loading, to jeśli pobierzesz z kontekstu EF obiekt Faktura, z bazy od razu wczytany zostanie także obiekt Klient.
  2. Jeśli będziesz korzystał z lazy loading, to pobierając Fakturę pobierzesz tylko ją. Ale jeśli gdzieś w kodzie napiszesz np.: string nip = faktura.Klient.Nip, to wówczas dopiero z bazy zostanie doczytany Klient.

Dodatkowo wydaje mi się, że gdy wywołasz Dispose na obiekcie DbContext (czyli na przykład obiekt typu DbContext zostanie stworzony poprzez blok using) to próba odwołania się do obiektu klient, który jeszcze nie został załadowany skończy się rzuceniem wyjątku. Jeśli się mylę to mnie poprawcie ;)

1
Mały Orzeł napisał(a):

Dodatkowo wydaje mi się, że gdy wywołasz Dispose na obiekcie DbContext (czyli na przykład obiekt typu DbContext zostanie stworzony poprzez blok using) to próba odwołania się do obiektu klient, który jeszcze nie został załadowany skończy się rzuceniem wyjątku. Jeśli się mylę to mnie poprawcie ;)

Nie wiem czy tak jest, ale jeśli tak nie jest, to bardzo źle. Na obiektach pobranych z bazy operujemy wewnątrz jednego unit of work, czyli podczas istnienia jednej instancji DbContextu.

0

dzięki wielkie za odpowiedzi rozjaśniło mi to sporo :)
a tak poza tematem znacie jakieś dobre książki do entity?

0
somekind napisał(a):

Na obiektach pobranych z bazy operujemy wewnątrz jednego unit of work, czyli podczas istnienia jednej instancji DbContextu.

łatwo mówić w przypadku aplikacji webowej gdzie w jednym zapytaniu możemy pozmieniać wszystkie dane
ale jak to jest w przypadku aplikacji desktopowej, gdzie w jednym miejscu pobierany encję, dajemy użytkownikowi możliwość przykładowo jej edycji i dopiero po kilkunastu minutach może nastąpić część z zapisem?

Czy musimy przepisywać encje z entity do jakiegoś innego obiektu a potem na podstawie tego obiektu z powrotem w funkcji zapisu szukać znów tego obiektu na bazie na podstawie jakiegoś ID, po czym przepisać z naszego obiektu do edycji do obiektu bazodanowego entity?
W przypadku gdy chcemy dodać możliwość lokalnego tworzenia / usuwania jakichś subelementów, kod może się nieco skomplikować.
Jak to powinno wyglądać?

0

W takich przypadkach korzystasz z jakiegoś wzorca projektowego np. MVP. Pobierasz enację z modelu, następnie przekazujesz ja do widoku (najlepiej wszystkie właściwości encji zbindować z kontrolkami), na akcję widoku reagujesz najpierw walidacją danych, dopiero potem tworzysz nowy DbContext i dodajesz go do niego przy pomocy Attach z flaga ustawioną na "zmodyfikowany" obiekt.

1
Wielki Pomidor napisał(a):

łatwo mówić w przypadku aplikacji webowej gdzie w jednym zapytaniu możemy pozmieniać wszystkie dane
ale jak to jest w przypadku aplikacji desktopowej, gdzie w jednym miejscu pobierany encję, dajemy użytkownikowi możliwość przykładowo jej edycji i dopiero po kilkunastu minutach może nastąpić część z zapisem?

Ten problem występuje, gdy masz jednowarstwową aplikację i łamiesz SRP używając tych samych obiektów w GUI i w ORM. To może dobre w malutkich, jednorazowych aplikacjach, ale nie gdy tworzymy coś poważnego.

Czy musimy przepisywać encje z entity do jakiegoś innego obiektu a potem na podstawie tego obiektu z powrotem w funkcji zapisu szukać znów tego obiektu na bazie na podstawie jakiegoś ID, po czym przepisać z naszego obiektu do edycji do obiektu bazodanowego entity?

Nie musimy (nikt nas za to nie zbije przecież ;)), ale to dobre rozwiązanie.

W przypadku gdy chcemy dodać możliwość lokalnego tworzenia / usuwania jakichś subelementów, kod może się nieco skomplikować.
Jak to powinno wyglądać?

Mógłbyś podać konkretny przykład, bo chyba nie rozumiem?

Można szukać różnych obejść, jak np. w poście wyżej, ja bym jednak tworzył aplikacje wielowarstwowo, nie łamał SRP i nie pisał aplikacji w sposób niewydajny.
Operowanie na tych samych modelach przy operowaniu na bazie i w GUI jest słabe, bo:

  1. Ewidentnie łamiemy SRP.
  2. To, co wyświetlamy w GUI często nie ma związku z tym, co jest w bazie. Zazwyczaj nie potrzebujemy wyświetlać użytkownikowi danych z wszystkich kolumn, więc po co je pobierać? Z drugiej strony często w GUI pokazujemy dane z dwóch obiektów (jak w moim przykładzie). Łączenie obiektów w kodzie GUI jest zazwyczaj trudniejsze, i znacznie mniej wydajne niż zrobienie tego na poziomie najbliższym bazy danych.
  3. Obiekt między odczytem a zapisem mógł zostać zmieniony przez innego użytkownika. To też trzeba obsłużyć (np. przez optimistic concurrency). Żeby to zrealizować musimy obiekt z bazy i tak pobrać jeszcze raz przed zapisem.

Odczyt z bazy i zapis do bazy wykonumemy w oddzielnych UoW, a obiekty ORM to nie ViewModele. Nieważne, czy to aplikacja webowa, desktopowa, konsolowa czy jakaś jeszcze inna. Ja wiem, że to duża zaleta desktopowych aplikacji, że możemy trzymać obiekty w pamięci, nie to co w web, ale nie wpadajmy w tę pułapkę!

P.S. Obiekt ORM to nie jest encja tylko persistence model. (Entity Framework ma zjebaną nawet nazwę.)

0
somekind napisał(a):

Obiekt ORM to nie jest encja tylko persistence model. (Entity Framework ma zjebaną nawet nazwę.)

Moim zdaniem chodzi o encje w sensie "byt" / "obiekt domenowy", nie o polską definicję encji więc nazwa jest w porządku

Czyli używanie obiektów entity frameworka jako modelu w aplikacji Twoim zdaniem łamie zasadę SRP i oznacza architekturę jednowarstwową?
No ale przecież obiekty dla entity framework mogą implementować interfejsy, mogą być partial i nie widzę przeszkód żeby używać ich jako modeli w ViewModelach dla GUI?
Możesz jakoś zaargumentować swoją wypowiedź?

0
Wielki Pomidor napisał(a):

Moim zdaniem chodzi o encje w sensie "byt" / "obiekt domenowy", nie o polską definicję encji więc nazwa jest w porządku

No właśnie - "obiekt domenowy". Obiekty z ORMa to nie są obiekty domenowe. One nie modelują domeny tylko strukturę rekordów w bazie.

Czyli używanie obiektów entity frameworka jako modelu w aplikacji Twoim zdaniem łamie zasadę SRP i oznacza architekturę jednowarstwową?

Używanie obiektów EF w GUI łamie SRP (bo obiekt ma dwie odpowiedzialności - definiuje strukturę danych w bazie oraz wygląd UI. I tak, to świadczy o jednowarstwowości, bo w wielowarstwowej aplikacji jest to nieosiągalne.

Możesz jakoś zaargumentować swoją wypowiedź?

Trzy argumenty dałem w poprzednim poście.

0
somekind napisał(a):

Używanie obiektów EF w GUI łamie SRP (bo obiekt ma dwie odpowiedzialności - definiuje strukturę danych w bazie oraz wygląd UI

Bycie kontenerem dla dwóch oddzielnych obiektów nie oznacza że obiekt ma dwie odpowiedzialności. Dopóki nie ma jakiegoś zachowania to w ogóle nie ma żadnej odpowiedzialności - inaczej byśmy musieli mówić że wbudowane struktury jak DateTime łamią zasadę SRP bo używamy ich we wszystkich warstwach do różnych celów

Poza tym powiedzmy używamy interfejsu IUser mającego pola Name i LastName, definiujemy "encję" User w warstwie entity frameworka (DAL) implementującą ten interfejs - ta klasa User dopiero definiuje pośrednio strukturę danych w bazie (a tak właściwie mówiąc to przy pomocy refleksji struktura bazy danych jest wzorowana na tym obiekcie)
Z drugiej strony mamy ViewModel przyjmujący obiekt typu zgodnego z IUser i służy za proxy między tym obiektem a GUI wprowadzając jednocześnie walidację wpisywanych danych

Nie widzę tu łamania zasady SRP, nie widzę wymuszenia jednej warstwy i nie widzę sensu przepisywania wszystkich potrzebnych propertów z obiektu z entity framework do jakiegoś POCO tylko po to ( ;) ) żeby nie użyć tego samego obiektu - nie ważne czy robimy to ręcznie czy za pomocą mappera - lepiej moim zdaniem te potrzebne nam w modelu biznesowym property wydzielić do interfejsu

0

Coyote mi zepsuł kawał
Miało pisać POTO

0
Wielki Pomidor napisał(a):

Bycie kontenerem dla dwóch oddzielnych obiektów nie oznacza że obiekt ma dwie odpowiedzialności.
Dopóki nie ma jakiegoś zachowania to w ogóle nie ma żadnej odpowiedzialności - inaczej byśmy musieli mówić że wbudowane struktury jak DateTime łamią zasadę SRP bo używamy ich we wszystkich warstwach do różnych celów

Dziwna filozofia, zwłaszcza tekst o DateTime.

Obiekt powinien mieć jeden powód do zmiany. Przy Twoim podejściu masz dwa powody: zmianę w sposobie przechowywania danych, albo zmianę w sposobie wyświetlania w GUI. To nie jest SRP.

Poza tym powiedzmy używamy interfejsu IUser mającego pola Name i LastName, definiujemy "encję" User w warstwie entity frameworka (DAL) implementującą ten interfejs - ta klasa User dopiero definiuje pośrednio strukturę danych w bazie (a tak właściwie mówiąc to przy pomocy refleksji struktura bazy danych jest wzorowana na tym obiekcie)
Z drugiej strony mamy ViewModel przyjmujący obiekt typu zgodnego z IUser i służy za proxy między tym obiektem a GUI wprowadzając jednocześnie walidację wpisywanych danych

Czyli ViewModel, który jest de facto tylko walidatorem do obiektu ORMa pod kątem danych wprowadzanych przez użytkownika? Brzmi strasznie.

Nie widzę tu łamania zasady SRP, nie widzę wymuszenia jednej warstwy i nie widzę sensu przepisywania wszystkich potrzebnych propertów z obiektu z entity framework do jakiegoś POCO

No cóż, większość programistów nigdy nigdzie nie widzi łamania SRP.
A jeśli GUI aplikacji wie cokolwiek o bazie danych, to jest to jedna warstwa z definicji.

Ponieważ mamy jeden uniwersalny obiekt, to jest używany na wszystkich widokach w aplikacji. W efekcie mnożą się tam właściwości służące do wyświetlania czegoś (bo np. raz potrzebujemy formatu "imię nazwisko", a innym razem "nazwisko imię") i inne metody mające zastosowanie tylko w jednym miejscu. Do tego dochodzą jeszcze metody biznesowe (no bo w końcu nasz ORM model udaje encję domenową), walidacyjne (bo nie możemy zrobić prawidłowego always valid entity z walidacją w konstruktorze, lecz musimy mieć publiczne właściwości, bo to przecież EF), na dodatek walidacja jest kontekstowa, bo na każdym ekranie może być inna, itd...
W efekcie mamy mega spaghetti god object. No bo po co tworzyć oddzielne klasy do różnych celów?

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