Odświeżanie danych w DbContext

0

Witam,

mam pewien problem i zarazem pytanie.
Chodzi o to, że wykonuję pewną operację w mojej aplikacji używającej EF np. logowanie do systemu. Operacje sprawdzania poprawności danych logowania, sprawdzenie czy użytkownik nie jest zablokowany itp wykonywane są w serwisie UserService. W tym serwisie jest jedno pole typu DbContext, które jest przez konstruktor wstrzykiwane poprzez Unity Container, a tam zarejestrowane w ten sposób:

Container.RegisterType<SmContext>(new ContainerControlledLifetimeManager());

Problem pojawia się, kiedy mam odpalone okno logowania i próbuję się zalogować do zablokowanego konta to wyświetla mi odpowiedni komunikat. Jeśli natomiast obok na innym komputerze z drugiej instancji programu odblokuje to konto, to póki nie zrestartuje programu to dane się nie zmieniają. Obszedłem to w ten sposób, że napisałem klasę rozszerzeniową:

public static void ReloadEntity<TEntity>(
            this DbContext context,
            TEntity entity)
            where TEntity : class
        {
            context.Entry(entity).Reload();
        }

I odpalam ją przed logowaniem.

Wcześniej w każdej metodzie serwisu miałem using i korzystałem z nowego obiektu kontekstu. Jest jakiś sposób, aby było dobrze?

1

Zarejestrowałeś serwis jako singleton i zapewne nie odświeżasz stanu obiektu. Możesz zrobić sobie metodę, która będzie sprawdzała status konta oraz uprawnienia podczas wykonywania każdej akcji.

Zawsze można też ustawić flagę w encji użytkownika, która mówi, że jeśli uprawnienia zostały zmienione to należy wylogować użytkownika. Sprawdzałbyś to przez jakąś metodę, np. klasy nadrzędnej, żeby się nie powtarzać, albo przez jakiś timer, etc... Musisz zadbać też o to żeby każda zmiana uprawnień w programie przestawiała flagę. Nazywać się może po prostu public bool IsLogoutNeeded { get; set; }.

0

Serwis mam normalnie zarejestrowany z interfejsem. Jako singleton mam dbcontext.

Nie do końca rozumiem o co Ci chodzi z tą metodą. Mam okno logowania. I moja metoda logowania właśnie za każdym razem odwołuje się bezpośrednio do dbcontextu i sprawdza, czy użytkownik nie ma zablokowanego konta. Jeśli ma zablokowane, to zwraca odpowiedni status, jeśli konto jest odblokowane to pozwala się zalogować.
Problem jest w przypadku, kiedy włączę program, próbuję się zalogować zablokowanym użytkownikiem. Błąd, ok. Wchodzę do SSMS. Zmieniam w bazie false na true. Nie zamykam w tym czasie mojej aplikacji. Próbuję się logować, dalej to samo. Dopiero zamknięcie aplikacji odświeża status konta. Tutaj ten singleton bardzo nie gra roli, bo nawet gdybym w serwisie UserService robił nową instancję kontekstu to tak czy inaczej, okna nie zamykam więc obiekt serwisu wciąż jest ten sam. Na siłę pomogłoby tworzenie na nowo kontekstu w obrębie tej jednej metody lub użycie właśnie metody, która mi odświeża encję jak powyżej w moim poście. Jestem ciekaw, jak jest poprawnie. Nawet jeśli chodzi o używanie kontekstu w serwisach, jak je wstrzykiwać, czy jako singleton, czy w każdej metodzie używać using, jak to się robi produkcyjnie. Dobrze by było, aby dało się takie metody serwisu przetestować.

3

Nie wiem, wg mnie robisz strasznie dużo niepotrzebnych rzeczy. Po co rejestrujesz context jako singleton? Czemu to ma służyć w ogóle? Przecież to nie jest żadna serwisowa zależność czy coś. Rejestruj w kontenerze zależności w postaci serwisów, a nie szczegóły implementacji, które niepotrzebnie w ten sposób wypływają Ci poza serwisy. Szczegół implementacji w postaci ORM'a wycieka Ci poza serwis.

To jest bez sensu, a tak miałbyś po prostu serwis, który posiada metodę sprawdzającą czy user ma możliwość zalogowania czy nie ma. Zrobiłbyś w metodzie zapytanie EF, normalnie z using i wszystko ładnie by działało, a Ty robisz jakieś naprawdę niestworzone kombinacje dla bardzo prostej rzeczy do sprawdzenia. Przemyśl to jeszcze raz.

Nawet jeśli chodzi o używanie kontekstu w serwisach...

Nie wstrzykuj ich tylko zwyczajnie twórz using(var db = new Context()) i używaj. Po co kombinować i rejestrować takie rzeczy w IoC?

0

Ok, czyli mam wewnątrz każdej metody serwisu używać

using (var db = new DbContext())

?

0

Tak, bo po co używać stałego, żyjącego wciąż DbContextu? Nawet przez zwykłe ADO zapytanie realizujesz w ten sposób, że łączysz się, wykonujesz i rozłączasz.
Tak samo tutaj: tworzysz kontekst, używasz kwerend i zapominasz.

Można by nawet pokusić się o stwierdzenie, że DbContext żyjący jako singleton to nic innego jak wyciek pamięci. Dlaczego? Bo masz tam choćby kolekcje, które wypełniasz danymi, a których nie czyścisz kiedy przestajesz używać. Po co więc tak robić?

2

@grzesiek51114: ale przecież nic nie szkodzi zarejestrować contextu jako transient, nie musieć pisać new i nie mieć problemu z singletonem.

0

@somekind: tak ale po co w ogóle rejestrować takie rzeczy w IoC?

2

Wstrzykuj context skonfigurowany jako transient (lub scoped w aplikacji webowej).

@grzesiek51114: po to żeby zastosować Inversion of Control i Dependency Injection?

0

No można w sumie. Jakby się uprzeć to wszystko można tak wstrzykiwać tylko pytanie czy to nie jest wyciek implementacji?

@somekind, @Aventus
EDIT: chyba, że mamy także IoC po stronie serwisów. Jeśli wszystko zamknięte jest w jednym pudełku to ok.

0

Ok może faktycznie, wcześniej miałem problem z tym, że podczas korzystania z różnych instancji dbcontextu czyli przypisania powiedzmy użytkownikowi z jednego contextu grupy z innej instancji dbcontextu działy się dziwne rzeczy. O tu pisałem .

Ostatecznie rozwiązałem to w mapowaniach, że do nowo tworzonego użytkownika przekazuję tylko id, bez całego obiektu grupy. A ten singleton to pozostałość.

No dobra, mam usinga w środku metody, da się potem napisać test do takiej metody? Pytam, bo nie wiem.

1

@lukaszek016: oni mają rację. Łatwiej będzie mockować kontekst jeśli zarejestrujesz go w IoC i będziesz wstrzykiwać do konstruktora serwisu. Tylko umówmy się, nie jako singleton. :-)

Opcja z using jest fajna i wygodna, bo jest w większości tutoriali ale pod kątem mocków to chyba lepiej zrobić transient scope w IoC. Później w testach starczy wstrzyknąć takiego mocka do serwisu i działać, a w "produkcyjnym" kodzie skorzystać z dobrodziejstw IoC.

0

@grzesiek51114: Ty po prostu lubisz edytować posty ;)

No właśnie z tego względu zdecydowałem się brać dbcontext z kontenera. Ale jak już wspomniałem singleton - pozostałość po poprzednim kombinowaniu z instancją dbcontextu. Dzięki za pomoc wszystkim.

0
grzesiek51114 napisał(a):

@somekind: tak ale po co w ogóle rejestrować takie rzeczy w IoC?

Żeby potem móc łatwo zmienić connection string albo inną konfigurację tudzież zarejestrować jakieś rozszerzenie. Przy rejestracji w kontenerze robisz to w jednym miejscu, a mając new w każdej klasie prowadzisz do małpowania kodu.

grzesiek51114 napisał(a):

No można w sumie. Jakby się uprzeć to wszystko można tak wstrzykiwać tylko pytanie czy to nie jest wyciek implementacji?

Niby dlaczego? To tylko jawne stwierdzenie, że jakiś serwis zależy od jakiegoś ORMa. Wyciek implementacji miałbyś, gdyby warstwa nad serwisami korzystała z ORMa bezpośrednio.

@somekind, @Aventus
EDIT: chyba, że mamy także IoC po stronie serwisów. Jeśli wszystko zamknięte jest w jednym pudełku to ok.

Nie wiem co to IoC po stronie serwisów, jak mam w projekcie kontener, to wstrzykuję z niego publiczne obiekty ze wszystkich warstw (kontrolery, walidatory, serwisy, orm, itp.).

0

@somekind:

Nie wiem co to IoC po stronie serwisów, jak mam w projekcie kontener, to wstrzykuję z niego publiczne obiekty ze wszystkich warstw (kontrolery, walidatory, serwisy, orm, itp.).

Hmm... tutaj dałeś mi do myślenia. U siebie, w projektach mam kontener IoC po stronie aplikacji czyli nie jest posadowiony w projekcie, w którym są serwisy czy interfejsy, a jest tam, gdzie jest projekt, który z serwisów korzysta, czyli np. po stronie finalnej aplikacji z GUI. Może zrobić osobny projekt, tylko z kontenerem IoC i tam rejestrować wszystkie potrzebne rzeczy? Zastanawiam się.

A z pytaniem to chodziło mi o to, że skoro posiadam jeden kontenerek w projekcie finalnej aplikacji z GUI, gdzie rejestruję serwisy, z których korzystam, to może by tak doinstalować drugi kontener po stronie projektu z serwisami i w nim rejestrować tylko rzeczy, które potrzebne są do implementacji serwisów. Tylko to trochę wydaje mi się przerostem formy nad treścią.

1

@grzesiek51114: ale po co robić IoC tylko w warstwie GUI? Co tam się wstrzykuje, jak tam praktycznie nic w porównaniu z resztą aplikacji nie ma?

Ja korzystam z Autofaca, który obsługuje moduły. Każdy projekt (w sensie VS csproj) ma swój moduł, w którym rejestruje swoje typy, następnie aplikacja startując skanuje w poszukiwaniu modułów Autofaca w załadowanych dllkach i na tej podstawie rejestruje wszystkie zależności: https://autofaccn.readthedocs.io/en/latest/register/scanning.html#scanning-for-modules
Kontener jest jeden, nie ma referencji między dllkami z różnych warstw, wszystko działa.

Jest też podejście z oddzielnym projektem mającym referencje do wszystkich innych i inicjalizującym kontener IoC, mnie jakoś nie przekonuje, ale może warto spróbować.

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