NIE używaj wzorca repozytorium, użyj ORM w stanie (EF)

96

Zawsze używałem wzorca Repozytorium, ale w moim najnowszym projekcie chciałem sprawdzić, czy uda mi się go udoskonalić i wdrożyć „Jednostkę Pracy”. Im bardziej zacząłem kopać, zacząłem zadawać sobie pytanie: "Czy naprawdę tego potrzebuję?"

Teraz wszystko zaczyna się od kilku komentarzy na temat Stackoverflow ze śladem posta Ayende Rahien na swoim blogu, z dwoma konkretnymi,

Można by o tym mówić w nieskończoność i to zależy od różnych zastosowań. Co chciałbym wiedzieć,

  1. czy to podejście byłoby odpowiednie dla projektu Entity Framework?
  2. Czy przy użyciu tego podejścia logika biznesowa nadal działa w warstwie usług lub metodach rozszerzających (jak wyjaśniono poniżej, wiem, że metoda rozszerzenia wykorzystuje sesję NHib)?

Można to łatwo zrobić przy użyciu metod rozszerzających. Czyste, proste i wielokrotnego użytku.

public static IEnumerable GetAll(
    this ISession instance, Expression<Func<T, bool>> where) where T : class
{
    return instance.QueryOver().Where(where).List();
}

Używając tego podejścia i Ninjectjako DI, muszę stworzyć Contextinterfejs i wstrzyknąć go do moich kontrolerów?

Dejan.S
źródło

Odpowiedzi:

104

Przeszedłem wiele ścieżek i stworzyłem wiele implementacji repozytoriów w różnych projektach i ... Rzuciłem ręcznik i poddałem się, oto dlaczego.

Kodowanie wyjątku

Czy kodujesz pod kątem 1% szansy, że Twoja baza danych zmieni się z jednej technologii na inną? Jeśli myślisz o przyszłym stanie swojej firmy i mówisz, że tak, to jest taka możliwość, a) muszą mieć dużo pieniędzy, aby pozwolić sobie na migrację do innej technologii DB lub b) wybierasz technologię DB dla przyjemności lub c ) coś poszło nie tak z pierwszą technologią, z której zdecydowałeś się skorzystać.

Po co wyrzucać bogatą składnię LINQ?

LINQ i EF zostały opracowane, aby można było z nimi robić fajne rzeczy, aby odczytywać i przechodzić przez wykresy obiektów. Tworzenie i utrzymywanie repozytorium, które daje taką samą elastyczność, jest potwornym zadaniem. Z mojego doświadczenia wynika, że ​​za każdym razem, gdy tworzyłem repozytorium, ZAWSZE miałem wyciek logiki biznesowej do warstwy repozytorium, aby zwiększyć wydajność zapytań i / lub zmniejszyć liczbę trafień do bazy danych.

Nie chcę tworzyć metody dla każdej permutacji zapytania, które muszę napisać. Równie dobrze mógłbym napisać procedury składowane. Nie chcę GetOrder, GetOrderWithOrderItem, GetOrderWithOrderItemWithOrderActivity, GetOrderByUserId, i tak dalej ... Po prostu chcę dostać główną jednostkę i trawers i zawierają wykres obiektu jak ja więc proszę.

Większość przykładów repozytoriów to bzdury

Chyba że tworzysz coś NAPRAWDĘ podstawowego, takiego jak blog lub coś, co Twoje zapytania nigdy nie będą tak proste, jak 90% przykładów, które znajdziesz w Internecie, otaczających wzór repozytorium. Nie mogę tego wystarczająco podkreślić! To jest coś, co trzeba przeczołgać się przez błoto, żeby się dowiedzieć. Zawsze będzie jedno zapytanie, które zepsuje twoje doskonale przemyślane repozytorium / rozwiązanie, które stworzyłeś, i dopiero w tym momencie, gdy ponownie zgadujesz siebie i zaczyna się dług techniczny / erozja.

Nie testuj mnie jednostkowo, bracie

Ale co z testami jednostkowymi, jeśli nie mam repozytorium? Jak będę kpić? Proste, nie masz. Spójrzmy na to z obu stron:

Brak repozytorium - możesz mockować przy DbContextużyciu IDbContextlub innych sztuczek, ale wtedy naprawdę testujesz jednostkowo LINQ to Objects, a nie LINQ to Entities, ponieważ zapytanie jest określane w czasie wykonywania ... OK, więc to nie jest dobre! Więc teraz to zależy od testu integracji, aby to objąć.

Z repozytorium - możesz teraz mockować swoje repozytoria i testować jednostki między warstwami. Świetnie, prawda? Cóż, nie do końca ... W powyższych przypadkach, w których musisz przeciekać logikę do warstwy repozytorium, aby zapytania były bardziej wydajne i / lub mniej trafień do bazy danych, jak testy jednostkowe mogą to pokryć? Jest teraz w warstwie repozytorium i nie chcesz testować, IQueryable<T>prawda? Bądźmy również szczerzy, twoje testy jednostkowe nie będą obejmować zapytań, które mają .Where()klauzulę 20 linii i.Include()To zbiór relacji i ponownie trafia do bazy danych, aby wykonać wszystkie inne czynności, bla, bla, bla tak czy inaczej, ponieważ zapytanie jest generowane w czasie wykonywania. Ponieważ utworzyłeś repozytorium, aby utrzymać ignorancję w zakresie trwałości wyższych warstw, jeśli chcesz teraz zmienić technologię bazy danych, przepraszam, że testy jednostkowe na pewno nie zagwarantują tych samych wyników w czasie wykonywania, wracając do testów integracyjnych. Więc cały punkt repozytorium wydaje się dziwny ...

2 centy

Utraciliśmy już wiele funkcji i składni, gdy używamy EF zamiast zwykłych procedur składowanych (wstawianie zbiorcze, usuwanie zbiorcze, CTE itp.), Ale koduję również w C #, więc nie muszę wpisywać binarnego. Używamy EF, dzięki czemu możemy mieć możliwość korzystania z różnych dostawców i pracy z grafami obiektów w przyjemny, powiązany sposób między wieloma rzeczami. Niektóre abstrakcje są przydatne, a niektóre nie.

Ryan
źródło
18
Nie tworzysz repozytoriów, aby móc je testować jednostkowo. Tworzysz repozytoria, aby móc testować jednostkowo logikę biznesową . Jeśli chodzi o upewnienie się, że zapytania działają: znacznie łatwiej jest pisać testy integracyjne dla repozytoriów, ponieważ zawierają one tylko logikę, a nie biznes.
jgauffin
16
Coding for the exception: Używanie repozytoriów nie oznacza możliwości zmiany silnika bazy danych. Chodzi o oddzielenie biznesu od wytrwałości.
jgauffin
2
To wszystko są bardzo ważne punkty, za którymi stoi wiele prawdy. Brakuje jednak uświadomienia sobie, że LINQ porozrzucany wokół aplikacji zamiast ograniczania do spójnej lokalizacji tworzy odpowiednik EF wywołań SQL na stronach codebehind. Każde zapytanie LINQ jest potencjalnym punktem konserwacji w aplikacji, a im więcej jest (i im bardziej są one rozpowszechnione), tym wyższe są koszty utrzymania i ryzyko. Wyobraź sobie, że dodajesz flagę „usunięty” do jednostki i musisz zlokalizować każde miejsce w dużej aplikacji, do której jednostka jest odpytywana, i trzeba ją zmodyfikować ...
DVK
2
Myślę, że to jest krótkowzroczne i zblazowane. Dlaczego miałbyś wyciekać logikę do repozytorium? A jeśli tak, dlaczego miałoby to znaczenie? To implementacja danych. Wszystko, co robimy, to odgradzanie LINQ od reszty kodu, ukrywając go za repozytorium. Mówisz, że tego nie testuj, ale nie możesz tego przetestować jako argument przeciwko temu. Zrób więc repozytorium, nie ujawniaj IQueryable i nie testuj go. Przynajmniej możesz przetestować wszystko inne w oderwaniu od implementacji danych. I ten 1% szansa na zmianę db to wciąż olbrzymia wartość $.
Sinaesthetic
5
+1 za tę odpowiedź. Uważam, że naprawdę NIE potrzebujemy repozytoriów z Entity Framework Core. To DbSetjest repozytorium i DbContextjest jednostką pracy . Po co implementować wzorzec repozytorium, skoro ORM już to robi za nas! Do testów wystarczy zmienić dostawcę na InMemory. I zrób swoje testy! Jest to dobrze udokumentowane w MSDN.
Mohammed Noureldin
49

Wzorzec repozytorium jest abstrakcją . Ma to na celu zmniejszenie złożoności i uczynienie reszty kodu trwałą ignorancją. Jako bonus umożliwia pisanie testów jednostkowych zamiast testów integracyjnych .

Problem polega na tym, że wielu programistów nie rozumie celu wzorców i nie tworzy repozytoriów, które przeciekają określone informacje do dzwoniącego (zwykle przez ujawnienie IQueryable<T>). W ten sposób nie odnoszą korzyści z bezpośredniego korzystania z sali operacyjnej / M.

Zaktualizuj, aby zająć się inną odpowiedzią

Kodowanie wyjątku

Korzystanie z repozytoriów nie polega na możliwości przełączania technologii trwałości (tj. Zmiany bazy danych lub korzystania z usługi sieciowej itp.). Chodzi o oddzielenie logiki biznesowej od trwałości, aby zmniejszyć złożoność i powiązania.

Testy jednostkowe a testy integracyjne

Nie piszesz testów jednostkowych dla repozytoriów. Kropka.

Ale wprowadzając repozytoria (lub jakąkolwiek inną warstwę abstrakcji między trwałością a biznesem), możesz pisać testy jednostkowe dla logiki biznesowej. tzn. nie musisz się martwić, że testy zakończą się niepowodzeniem z powodu nieprawidłowo skonfigurowanej bazy danych.

Co do pytań. Jeśli używasz LINQ, musisz również upewnić się, że zapytania działają, tak samo jak w przypadku repozytoriów. i odbywa się to za pomocą testów integracyjnych.

Różnica polega na tym, że jeśli nie pomieszałeś swojej firmy z instrukcjami LINQ, możesz mieć 100% pewności, że to Twój kod trwałości zawodzi, a nie coś innego.

Jeśli przeanalizujesz swoje testy, zobaczysz również, że są one znacznie czystsze, jeśli nie masz mieszanych obaw (np. Logika LINQ + Business)

Przykłady repozytoriów

Większość przykładów to bzdury. to jest bardzo prawdziwe. Jeśli jednak wygooglujesz jakiś wzór projektu, znajdziesz wiele kiepskich przykładów. Nie ma powodu, aby unikać używania wzoru.

Zbudowanie poprawnej implementacji repozytorium jest bardzo łatwe. W rzeczywistości musisz przestrzegać tylko jednej zasady:

Nie dodawaj niczego do klasy repozytorium aż do momentu, gdy tego potrzebujesz

Wielu programistów jest leniwych i próbuje utworzyć ogólne repozytorium i użyć klasy bazowej z wieloma metodami, których mogą potrzebować. YAGNI. Piszesz klasę repozytorium raz i przechowujesz ją tak długo, jak działa aplikacja (może to być lata). Po co to spieprzyć będąc leniwym. Utrzymuj go w czystości bez dziedziczenia klasy bazowej. Ułatwi to czytanie i konserwację.

(Powyższe stwierdzenie jest wskazówką, a nie prawem. Klasę bazową można bardzo dobrze zmotywować. Pomyśl tylko, zanim ją dodasz, aby dodać ją z właściwych powodów)

Starocie

Wniosek:

Jeśli nie masz nic przeciwko umieszczaniu instrukcji LINQ w kodzie biznesowym ani nie przejmujesz się testami jednostkowymi, nie widzę powodu, aby nie używać Entity Framework bezpośrednio.

Aktualizacja

Napisałem na blogu zarówno o wzorcu repozytorium, jak io tym, co naprawdę oznacza „abstrakcja”: http://blog.gauffin.org/2013/01/repository-pattern-done-right/

Zaktualizuj 2

W przypadku pojedynczego typu encji z ponad 20 polami, w jaki sposób zaprojektujesz metodę zapytania, aby obsługiwała dowolną kombinację permutacji? Nie chcesz ograniczać wyszukiwania tylko po nazwie, a co z wyszukiwaniem za pomocą właściwości nawigacji, wystawiaj wszystkie zamówienia z pozycją z określonym kodem ceny, 3 poziomy wyszukiwania właściwości nawigacji. Cały powód IQueryablezostał wymyślony, aby móc skomponować dowolną kombinację wyszukiwania w bazie danych. W teorii wszystko wygląda świetnie, ale potrzeba użytkownika wygrywa nad teorią.

Ponownie: jednostka z ponad 20 polami jest nieprawidłowo modelowana. To istota BOGA. Rozbicie go.

Nie twierdzę, że IQueryableto nie było do zadawania pytań. Mówię, że nie jest to właściwe dla warstwy abstrakcji, takiej jak wzór repozytorium, ponieważ jest nieszczelna. Nie ma dostawcy LINQ To Sql w 100% kompletnego (takiego jak EF).

Wszystkie mają specyficzne dla implementacji rzeczy, takie jak używanie przyspieszonego / leniwego ładowania lub wykonywanie instrukcji SQL „IN”. Wystawienie IQueryablew repozytorium zmusza użytkownika do poznania tych wszystkich rzeczy. Zatem cała próba wyodrębnienia źródła danych kończy się całkowitą porażką. Po prostu dodajesz złożoność bez żadnych korzyści z bezpośredniego korzystania z OR / M.

Albo poprawnie zaimplementuj wzorzec repozytorium, albo po prostu go nie używaj.

(Jeśli naprawdę chcesz obsługiwać duże jednostki, możesz połączyć wzorzec Repozytorium ze wzorcem Specification . To daje pełną abstrakcję, którą można również przetestować).

jgauffin
źródło
6
Brak ujawnienia IQueryable prowadzi do ograniczonego wyszukiwania, a ludzie w końcu tworzą więcej metod Get dla różnych typów zapytań, co ostatecznie sprawia, że ​​repozytorium jest bardziej złożone.
Akash Kava
3
w ogóle nie rozwiązałeś podstawowego problemu: ujawnianie IQueryable przez repozytorium nie jest całkowitą abstrakcją.
jgauffin
1
Posiadanie obiektu zapytania, który zawiera całą potrzebną infrastrukturę do wykonania, jest sposobem na imo. Podajesz mu pola, które są wyszukiwanymi terminami i zwraca listę wyników. W ramach QO możesz robić, co chcesz. Jest to interfejs, który tak łatwo przetestować. Zobacz mój post powyżej. To jest najlepsze.
h.alex
2
Osobiście uważam, że sensowne jest również zaimplementowanie interfejsu IQueryable <T> w klasie Repository, zamiast ujawniać podstawowy zestaw w jednym z jego elementów członkowskich.
dark_perfect
3
@yat: jedno repozytorium na zagregowany katalog główny. Ale imho to nie jest zbiorczy korzeń i agregacja tabel, ale tylko zbiorczy element główny i agregaty . Rzeczywista pamięć może wykorzystywać tylko jedną tabelę lub wiele z nich, tj. Może nie być mapowaniem jeden-jeden między każdym agregatem a tabelą. Używam repozytoriów, aby zmniejszyć złożoność i usunąć wszelkie zależności z podstawowego magazynu.
jgauffin
28

IMO zarówno Repositoryabstrakcja, jak i UnitOfWorkabstrakcja zajmują bardzo cenne miejsce w każdym znaczącym rozwoju. Ludzie będą spierać się o szczegóły implementacji, ale tak jak jest wiele sposobów na oskórowanie kota, tak istnieje wiele sposobów na wdrożenie abstrakcji.

Twoje pytanie dotyczy konkretnie używania lub nie używania i dlaczego.

Jak bez wątpienia zdałeś sobie sprawę, że masz już oba te wzorce wbudowane w Entity Framework, DbContextjest UnitOfWorki DbSetjest Repository. Zasadniczo nie ma potrzeby testowania jednostek UnitOfWorklub Repositorysamych siebie, ponieważ po prostu ułatwiają one między klasami a podstawowymi implementacjami dostępu do danych. To, co będziesz musiał ciągle robić, to kpić z tych dwóch abstrakcji podczas testowania jednostkowego logiki usług.

Możesz kpić, fałszować lub cokolwiek innego, używając zewnętrznych bibliotek dodając warstwy zależności kodu (których nie kontrolujesz) między logiką wykonującą testowanie a logiką testowaną.

Więc punkt moll jest to, że posiadanie własnego abstrakcji dla UnitOfWorki Repositorydaje maksymalną kontrolę i elastyczność podczas szydząc swoje testy jednostkowe.

Wszystko bardzo dobrze, ale dla mnie prawdziwą mocą tych abstrakcji jest to, że zapewniają one prosty sposób zastosowania technik programowania zorientowanego na aspekt i przestrzegania zasad SOLID .

Masz więc swoje IRepository:

public interface IRepository<T>
    where T : class
{
    T Add(T entity);
    void Delete(T entity);
    IQueryable<T> AsQueryable();
}

I jego realizacja:

public class Repository<T> : IRepository<T>
    where T : class
{
    private readonly IDbSet<T> _dbSet;
    public Repository(PPContext context) 
    {
        _dbSet = context.Set<T>();
    }

    public T Add(T entity)
    { 
        return _dbSet.Add(entity); 
    }

    public void Delete(T entity)
    {
        _dbSet.Remove(entity); 
    }

    public IQueryable<T> AsQueryable() 
    {
        return _dbSet.AsQueryable();
    }
}

Jak dotąd nic niezwykłego, ale teraz chcemy dodać trochę logowania - łatwe dzięki dekoratorowi logowania .

public class RepositoryLoggerDecorator<T> : IRepository<T>
    where T : class
{
    Logger logger = LogManager.GetCurrentClassLogger();
    private readonly IRepository<T> _decorated;
    public RepositoryLoggerDecorator(IRepository<T> decorated)
    {
        _decorated = decorated;
    }

    public T Add(T entity)
    {
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString() );
        T added = _decorated.Add(entity);
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
        return added;
    }

    public void Delete(T entity)
    {
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
        _decorated.Delete(entity);
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
    }

    public IQueryable<T> AsQueryable()
    {
        return _decorated.AsQueryable();
    }
}

Wszystko gotowe i bez zmian w naszym istniejącym kodzie . Istnieje wiele innych przekrojowych problemów, które możemy dodać, takich jak obsługa wyjątków, buforowanie danych, walidacja danych lub cokolwiek innego, a podczas naszego procesu projektowania i budowania jest to najcenniejsza rzecz, jaką mamy, która pozwala nam dodawać proste funkcje bez zmiany istniejącego kodu to nasza IRepositoryabstrakcja .

Teraz wiele razy widziałem to pytanie w StackOverflow - „Jak sprawić, by Entity Framework działało w środowisku z wieloma dzierżawcami?”.

https://stackoverflow.com/search?q=%5Bentity-framework%5D+multi+tenant

Jeśli masz Repositoryabstrakcję, odpowiedź brzmi: „łatwo dodać dekorator”

public class RepositoryTennantFilterDecorator<T> : IRepository<T>
    where T : class
{
    //public for Unit Test example
    public readonly IRepository<T> _decorated;
    public RepositoryTennantFilterDecorator(IRepository<T> decorated)
    {
        _decorated = decorated;
    }

    public T Add(T entity)
    {
        return _decorated.Add(entity);
    }

    public void Delete(T entity)
    {
        _decorated.Delete(entity);
    }

    public IQueryable<T> AsQueryable()
    {
        return _decorated.AsQueryable().Where(o => true);
    }
}

IMO, zawsze powinieneś umieścić prostą abstrakcję na każdym komponencie strony trzeciej, do którego będą się odnosić w więcej niż kilku miejscach. Z tej perspektywy ORM jest idealnym kandydatem, ponieważ odwołuje się do niego w znacznej części naszego kodu.

Odpowiedź, która zwykle przychodzi na myśl, gdy ktoś mówi „dlaczego powinienem mieć abstrakcję (np. Repository) Na temat tej lub innej biblioteki innej firmy”, brzmi „dlaczego nie?”

Dekoratory PS są niezwykle łatwe do zastosowania przy użyciu kontenerów IoC, takich jak SimpleInjector .

[TestFixture]
public class IRepositoryTesting
{
    [Test]
    public void IRepository_ContainerRegisteredWithTwoDecorators_ReturnsDecoratedRepository()
    {
        Container container = new Container();
        container.RegisterLifetimeScope<PPContext>();
        container.RegisterOpenGeneric(
            typeof(IRepository<>), 
            typeof(Repository<>));
        container.RegisterDecorator(
            typeof(IRepository<>), 
            typeof(RepositoryLoggerDecorator<>));
        container.RegisterDecorator(
            typeof(IRepository<>), 
            typeof(RepositoryTennantFilterDecorator<>));
        container.Verify();

        using (container.BeginLifetimeScope())
        {
            var result = container.GetInstance<IRepository<Image>>();

            Assert.That(
                result, 
                Is.InstanceOf(typeof(RepositoryTennantFilterDecorator<Image>)));
            Assert.That(
                (result as RepositoryTennantFilterDecorator<Image>)._decorated,
                Is.InstanceOf(typeof(RepositoryLoggerDecorator<Image>)));
        }
    }
}
qujck
źródło
Co ciekawe, ta odpowiedź jest nadal aktualna w 2020 roku. Od tego czasu niewiele się zmieniło. Niewiele metod na zastąpienie RP
Volkan Güven
11

Po pierwsze, jak sugeruje pewna odpowiedź, sam EF jest wzorcem repozytorium, nie ma potrzeby tworzenia dalszej abstrakcji tylko po to, aby nazwać go repozytorium.

Mockable Repository for Unit Tests, czy naprawdę go potrzebujemy?

Pozwalamy EF komunikować się, aby przetestować bazę danych w testach jednostkowych, aby przetestować naszą logikę biznesową bezpośrednio względem testowej bazy danych SQL. Nie widzę żadnej korzyści z posiadania makiety żadnego wzorca repozytorium. Co naprawdę jest złego w wykonywaniu testów jednostkowych w testowej bazie danych? Ponieważ operacje masowe nie są możliwe i kończy się na pisaniu surowego kodu SQL. SQLite in memory jest idealnym kandydatem do wykonywania testów jednostkowych na rzeczywistej bazie danych.

Niepotrzebna abstrakcja

Czy chcesz utworzyć repozytorium tylko po to, aby w przyszłości łatwo zastąpić EF na NHbibernate itp. Lub cokolwiek innego? Brzmi świetnie, ale czy jest naprawdę opłacalny?

Linq zabija testy jednostkowe?

Chciałbym zobaczyć przykłady tego, jak może zabijać.

Dependency Injection, IoC

Wow, to świetne słowa, na pewno świetnie wyglądają w teorii, ale czasami trzeba wybrać kompromis między świetnym projektem a świetnym rozwiązaniem. Wykorzystaliśmy to wszystko i skończyło się na tym, że wyrzuciliśmy wszystko do kosza i wybraliśmy inne podejście. Rozmiar a szybkość (rozmiar kodu i szybkość rozwoju) ma ogromne znaczenie w prawdziwym życiu. Użytkownicy potrzebują elastyczności, nie obchodzi ich, czy Twój kod jest świetny w projektowaniu pod względem DI czy IoC.

Chyba że tworzysz Visual Studio

Wszystkie te wspaniałe projekty są potrzebne, jeśli tworzysz złożony program, taki jak Visual Studio lub Eclipse, który będzie rozwijany przez wiele osób i musi być wysoce konfigurowalny. Cały wspaniały wzorzec rozwoju pojawił się po latach rozwoju tych IDE i ewoluowały w miejscu, w którym wszystkie te wspaniałe wzorce projektowe mają tak duże znaczenie. Ale jeśli robisz prostą listę płac opartą na sieci lub prostą aplikację biznesową, lepiej jest rozwijać swój program z czasem, zamiast spędzać czas na tworzeniu go dla milionów użytkowników, gdzie będzie wdrażany tylko dla setek użytkowników.

Repozytorium jako widok filtrowany - ISecureRepository

Z drugiej strony repozytorium powinno być filtrowanym widokiem EF, który chroni dostęp do danych, stosując niezbędny wypełniacz w oparciu o aktualnego użytkownika / rolę.

Ale to komplikuje repozytorium jeszcze bardziej, ponieważ kończy się ono w ogromnej bazie kodu do utrzymania. W końcu ludzie tworzą różne repozytoria dla różnych typów użytkowników lub kombinacji typów jednostek. Nie tylko to, otrzymujemy również wiele DTO.

Poniższa odpowiedź to przykładowa implementacja Filtered Repository bez tworzenia całego zestawu klas i metod. Może nie odpowiadać bezpośrednio na pytanie, ale może być przydatne w wyprowadzeniu jednego.

Zastrzeżenie: Jestem autorem Entity REST SDK.

http://entityrestsdk.codeplex.com

Mając powyższe na uwadze, opracowaliśmy SDK, który tworzy repozytorium przefiltrowanego widoku w oparciu o SecurityContext, który zawiera filtry dla operacji CRUD. Tylko dwa rodzaje reguł upraszczają skomplikowane operacje. Pierwszy to dostęp do jednostki, a drugi to reguła odczytu / zapisu dla właściwości.

Zaletą jest to, że nie przepisujesz logiki biznesowej ani repozytoriów dla różnych typów użytkowników, po prostu blokujesz lub przyznajesz im dostęp.

public class DefaultSecurityContext : BaseSecurityContext {

  public static DefaultSecurityContext Instance = new DefaultSecurityContext();

  // UserID for currently logged in User
  public static long UserID{
       get{
             return long.Parse( HttpContext.Current.User.Identity.Name );
       }
  }

  public DefaultSecurityContext(){
  }

  protected override void OnCreate(){

        // User can access his own Account only
        var acc = CreateRules<Account>();

        acc.SetRead( y => x=> x.AccountID == UserID ) ;
        acc.SetWrite( y => x=> x.AccountID == UserID );

        // User can only modify AccountName and EmailAddress fields
        acc.SetProperties( SecurityRules.ReadWrite, 
              x => x.AccountName,
              x => x.EmailAddress);

        // User can read AccountType field
        acc.SetProperties<Account>( SecurityRules.Read, 
              x => x.AccountType);

        // User can access his own Orders only
        var order = CreateRules<Order>();
        order.SetRead( y => x => x.CustomerID == UserID );

        // User can modify Order only if OrderStatus is not complete
        order.SetWrite( y => x => x.CustomerID == UserID 
            && x.OrderStatus != "Complete" );

        // User can only modify OrderNotes and OrderStatus
        order.SetProperties( SecurityRules.ReadWrite, 
              x => x.OrderNotes,
              x => x.OrderStatus );

        // User can not delete orders
        order.SetDelete(order.NotSupportedRule);
  }
}

Te reguły LINQ są oceniane względem bazy danych w metodzie SaveChanges dla każdej operacji, a te reguły działają jako zapora przed bazą danych.

Akash Kava
źródło
3
Testowanie jednostkowe względem bazy danych oznacza, że ​​masz dodatkowe zewnętrzne wymagania dotyczące testów. Jeśli ta baza danych jest wyłączona, dane zostaną wyczyszczone lub cokolwiek stanie się z tą bazą danych, testy zakończą się niepowodzeniem. To nie jest pożądane. Konfiguracja repozytoriów, które ujawniają IQueryable, zajmuje około 2 minut. Nie marnuj czasu. Dlaczego zajęło ci to dużo czasu? Wszystko to zajmuje kilka minut. Powiem, że to wszystko działało świetnie do testowania jednostkowego moich złożonych zapytań w warstwie usług. To było takie miłe, że nie potrzebowałem bazy danych do połączenia. Uzyskanie fałszywej struktury z nuget zajęło około minuty. To nie zajmuje czasu.
user441521
@ user441521 Repozytoria z IQueryable 2 minuty na konfigurację? w którym świecie żyjesz, każde żądanie asp.net w naszej aktywnej witrynie jest obsługiwane w ciągu milisekund. Wyśmiewanie i podrabianie itp. Zwiększa złożoność kodu, co jest całkowitą stratą czasu. Testy jednostkowe są bezużyteczne, gdy jednostka nie jest zdefiniowana jako jednostka logiki biznesowej.
Akash Kava
7

Jest wiele dyskusji na temat tego, która metoda jest poprawna, więc patrzę na to, ponieważ obie są dopuszczalne, więc używam zawsze tej, która mi się najbardziej podoba (co nie jest repozytorium, UoW).

W EF UoW jest zaimplementowana za pośrednictwem DbContext, a DbSets to repozytoria.

Jeśli chodzi o pracę z warstwą danych, po prostu pracuję bezpośrednio na obiekcie DbContext, dla złożonych zapytań wykonam metody rozszerzające dla zapytania, które można ponownie wykorzystać.

Myślę, że Ayende ma również kilka postów o tym, jak wyodrębnianie operacji CUD jest złe.

Zawsze tworzę interfejs i mój kontekst dziedziczy po nim, więc mogę używać kontenera IoC dla DI.

Josh
źródło
Więc jak rozbudowane są metody rozszerzające? Powiedzmy, że muszę uzyskać stan innego elementu w moim rozszerzeniu? To jest teraz moje największe zmartwienie. Masz coś przeciwko pokazaniu przykładów metod rozszerzających?
Dejan.S
ayende.com/blog/153473/… i ayende.com/blog/153569/… . (To są recenzje architektury (Framework?) O nazwie s # arp lite. W większości dobra, ale nie zgadza się z repozytoriami i abstrakcjami CUD).
Josh
Oparty na NHibernate. Nie masz żadnych przykładów użycia EF? I znowu, kiedy muszę zadzwonić do innej jednostki, jak najlepiej to zrobić w statycznej metodzie rozszerzenia?
Dejan.S,
3
Wszystko to jest w porządku, dopóki właściwość obiektu domeny nie będzie musiała zostać nawodniona przez dane, które nie są przechowywane w bazie danych; lub musisz rzucić okiem na technologię, która jest bardziej wydajna niż twój rozdęty ORM. Ups! ORM po prostu NIE zastępuje repozytorium, jest szczegółem implementacyjnym jednego z nich.
cdaq
2

To, co najczęściej dotyczy EF, nie jest wzorcem repozytorium. Jest to wzorzec fasady (abstrakcja wywołań metod EF do prostszych, łatwiejszych w użyciu wersji).

EF jest tym, który stosuje wzorzec repozytorium (a także wzorzec jednostki pracy). Oznacza to, że EF jest tym, który wyodrębnia warstwę dostępu do danych, aby użytkownik nie miał pojęcia, że ​​ma do czynienia z serwerem SQLServer.

I przy tym, większość „repozytoriów” na EF nie jest nawet dobrymi Fasadami, ponieważ po prostu mapują, całkiem prosto, na pojedyncze metody w EF, nawet do punktu, w którym mają te same sygnatury.

Zatem dwa powody zastosowania tego tak zwanego wzorca „repozytorium” do EF to umożliwienie łatwiejszego testowania i ustanowienie podzbioru wywołań „gotowych” do niego. Nieźle samo w sobie, ale najwyraźniej nie jest repozytorium.

Aratirn
źródło
1

Linq jest obecnie „Repozytorium”.

ISession + Linq już jest repozytorium i nie potrzebujesz ani GetXByYmetod, aniQueryData(Query q) uogólnień. Będąc trochę paranoikiem do używania DAL, nadal wolę interfejs repozytorium. (Z punktu widzenia łatwości konserwacji nadal musimy mieć trochę fasad w stosunku do określonych interfejsów dostępu do danych).

Oto repozytorium, którego używamy - odłącza nas od bezpośredniego użycia nhibernate, ale zapewnia interfejs linq (jako dostęp ISession w wyjątkowych przypadkach, które ostatecznie podlegają refaktoryzacji).

class Repo
{
    ISession _session; //via ioc
    IQueryable<T> Query()
    {
        return _session.Query<T>();
    }
}
mikalai
źródło
Co robisz dla warstwy usług?
Dejan.S
Kontrolery zapytań repozytorium danych tylko do odczytu, po co dodawać dodatkową warstwę? Inną możliwością jest użycie „ContentService”, które w coraz większym stopniu jest repozytorium na poziomie usług: GetXByY itp. Itd. Dla operacji modyfikacji - usługi aplikacji to tylko abstrakcje nad przypadkami użycia - swobodnie używają BL i repozytorium ..
mikalai
Jestem przyzwyczajony do wykonywania warstwy usług dla logiki biznesowej. Nie jestem pewien, czy podążam za tym, co robisz z ContentService, proszę rozwinąć. Czy byłoby złą praktyką tworzenie klas pomocniczych jako „warstwy usług”?
Dejan.S
Przez „warstwę usługową” rozumiałem „usługi aplikacji”. Mogą korzystać z repozytorium i dowolnej innej publicznej części warstwy domeny. Warstwa usługowa nie jest złą praktyką, ale unikałbym tworzenia klasy XService tylko po to, aby dostarczyć wynik List <X>. Pole komentarza wydaje się zbyt krótkie, aby szczegółowo opisać usługi, przepraszamy.
mikalai
A co jeśli, powiedzmy obliczenie koszyka i musisz pobrać parametry ustawień aplikacji i określone parametry klienta, aby wykonać obliczenia, i jest to ponownie wykorzystywane w kilku miejscach w aplikacji. Jak sobie radzisz w tej sytuacji? klasa pomocnicza czy usługa aplikacji?
Dejan.S,
1

Repository (czy jak ktoś wybiera się nazwać) w tej chwili jest dla mnie przede wszystkim o abstrahując dala warstwy trwałości.

Używam go w połączeniu z obiektami zapytań, więc nie mam połączenia z żadną konkretną technologią w moich aplikacjach. A także bardzo ułatwia testowanie.

Więc zwykle mam

public interface IRepository : IDisposable
{
    void Save<TEntity>(TEntity entity);
    void SaveList<TEntity>(IEnumerable<TEntity> entities);

    void Delete<TEntity>(TEntity entity);
    void DeleteList<TEntity>(IEnumerable<TEntity> entities);

    IList<TEntity> GetAll<TEntity>() where TEntity : class;
    int GetCount<TEntity>() where TEntity : class;

    void StartConversation();
    void EndConversation();

    //if query objects can be self sustaining (i.e. not need additional configuration - think session), there is no need to include this method in the repository.
    TResult ExecuteQuery<TResult>(IQueryObject<TResult> query);
}

Ewentualnie dodaj metody asynchroniczne z wywołaniami zwrotnymi jako delegaty. Repo jest łatwa do wdrożenia rodzajowo , więc nie jestem w stanie dotknąć linię realizacji z aplikacji do aplikacji. Cóż, jest to prawda przynajmniej przy używaniu NH, zrobiłem to również z EF, ale sprawiło, że nienawidziłem EF. 4. Rozmowa jest początkiem transakcji. Bardzo fajnie, jeśli kilka klas współdzieli instancję repozytorium. Również w przypadku NH jedno repozytorium w mojej implementacji równa się jednej sesji otwieranej przy pierwszym żądaniu.

Następnie Query Objects

public interface IQueryObject<TResult>
{
    /// <summary>Provides configuration options.</summary>
    /// <remarks>
    /// If the query object is used through a repository this method might or might not be called depending on the particular implementation of a repository.
    /// If not used through a repository, it can be useful as a configuration option.
    /// </remarks>
    void Configure(object parameter);

    /// <summary>Implementation of the query.</summary>
    TResult GetResult();
}

Dla konfiguracji używam w NH tylko do przejścia w ISession. W EF nie ma sensu mniej więcej.

Przykładowe zapytanie to… (NH)

public class GetAll<TEntity> : AbstractQueryObject<IList<TEntity>>
    where TEntity : class
{
    public override IList<TEntity> GetResult()
    {
        return this.Session.CreateCriteria<TEntity>().List<TEntity>();
    }
}

Aby wykonać kwerendę EF, musisz mieć kontekst w bazie abstrakcyjnej, a nie w sesji. Ale oczywiście ifc byłby taki sam.

W ten sposób zapytania są same hermetyzowane i łatwe do przetestowania. A co najlepsze, mój kod opiera się tylko na interfejsach. Wszystko jest bardzo czyste. Obiekty domeny (biznesowe) są po prostu tym, że np. Nie ma mieszania obowiązków, jak przy użyciu wzorca aktywnego rekordu, który jest trudny do przetestowania i miesza kod dostępu do danych (zapytania) w obiekcie domeny, robiąc to, mieszając obawy (obiekt, który pobiera samo??). Każdy może nadal tworzyć POCO do przesyłania danych.

Podsumowując, takie podejście zapewnia wiele możliwości ponownego wykorzystania i prostoty kodu, przy czym nic nie mogę sobie wyobrazić. Jakieś pomysły?

Wielkie dzięki dla Ayende za jego świetne posty i nieustające poświęcenie. To jego pomysły tutaj (obiekt zapytania), nie moje.

h.alex
źródło
1
Podmioty trwałe (Twoje POCO) NIE są podmiotami biznesowymi / domenowymi. Celem repozytorium jest oddzielenie warstwy biznesowej (lub dowolnej) od trwałości.
MikeSW,
Nie widzę połączenia. Trochę się zgadzam ze stroną POCO, ale nie obchodzi mnie to. Nic nie stoi na przeszkodzie, aby mieć „autentyczne” POCO i nadal stosować to podejście.
h.alex
1
Podmioty wcale nie muszą być głupimi POCO. W rzeczywistości modelowanie logiki biznesowej w Entities jest tym, co robi cały czas tłum DDD. Ten styl rozwoju bardzo dobrze komponuje się z NH lub EF.
chris,
1

Dla mnie to prosta decyzja, składająca się ze stosunkowo niewielu czynników. Czynniki to:

  1. Repozytoria są przeznaczone dla klas domeny.
  2. W niektórych moich aplikacjach klasy domeny są takie same, jak moje klasy trwałości (DAL), w innych nie.
  3. Kiedy są takie same, EF już dostarcza mi repozytoria.
  4. EF zapewnia leniwe ładowanie i IQueryable. Lubie takie.
  5. Abstrakcje / „fasadowanie” / ponowne wdrażanie repozytorium przez EF zwykle oznacza utratę leniwego i IQueryable

Tak więc, jeśli moja aplikacja nie może uzasadnić # 2, oddzielnego modelu domeny i danych, zwykle nie będę zawracać sobie głowy numerem 5.

Kredowy
źródło