W ostatnim wpisie poruszyłem tematykę IoC i w przykładowym kawałku kodu dla LocationRepo znajduje się metoda GetLocations zwracająca typ IQueryable. Zwróciła na to uwagę pewna osoba odwiedzająca bloga i słusznie. Stwierdziłem więc, że lepiej będzie rozpisać się krótko na ten temat niż edytować poprzedni wpis. No więc dlaczego zwracanie typu IQueryable<T> jest złe? Można by powiedzieć, że zwracanie ogólnie jest złe i czasem się zdarza 😉 ale do rzeczy.
Implementując repozytorium powinniśmy tworzyć nową metodę dla każdego zapytania, np. GetLocationsByName, GetLocationsBySth, itd. Nie jest dobrą praktyką utworzenie jednej metody albo właściwości zwracającej całą kolekcję typu IQueryable<T>, po to by następnie filtrować elementy przy pomocy LINQ już poza repozytorium. Zapytania LINQ stosowane dla kolekcji IQueryable<T> są tłumaczone na drzewa wyrażeń, które następnie tłumaczone są na język określonego źródła danych, np. SQL. Zwracając z repozytorium taki typ, odpowiedzialność za tworzenie zapytań do źródła danych przechodzi na dewelopera implementującego kod w warstwie wyższej, warstwie domeny. W konsekwencji kod taki nie jest testowalny w 100%. Nie znamy wszystkich możliwych kombinacji zapytań, które mogą występować.
Kolejnym argumentem, który przemawia za niestosowaniem typu IQueryable w repozytoriach, jest to że uzależniamy nasze repozytorium od konieczności używania Entity Frameworka albo LINQ to SQL, ponieważ w przypadku innych źródeł danych musiałby być zaimplementowany mechanizm zwracania IQueryable. Chcemy aby nasz kod był uniwersalny i nie chcemy utrudniać sobie życia implementując dla każdego nowego źródła danych IQueryable, więc jedynym wyjściem jest unikanie tego typu. Oczywiście czasami może się on przydać, jednak musimy mieć to wszystko na uwadze 😉
Poniżej przykład metod zawartych w repozytorium.
Tak nie robimy
1 2 3 4 |
public IQueryable<Location> GetLocations() { return _ctx.Locations.AsNoTracking(); } |
lepiej zróbmy to tak
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public IEnumerable<Location> GetLocations(int page, int itemsPerPage) { return _ctx.Locations.AsNoTracking() .OrderBy(l => l.Name) .Skip(itemsPerPage*(page - 1)) .Take(itemsPerPage) .ToList(); } public Location GetLocationByName(string name) { return _ctx.Locations.SingleOrDefault(l => l.Name == name); } |