Radzenie sobie z zapytaniami w repozytorium z wieloma konkretnymi implementacjami?

To jest bardziej naukowa ciekawość, ale staram się dowiedzieć, jak najlepiej osiągnąć następujące cele.

Wyobraź sobie sytuację, w której masz Person obiekt

public class Person {
    public string Name {get;set;}
    public int Age {get;set;}
}

I kontrakt repozytorium na pobieranie ich z jakiegoś sklepu persistence...

public class IPersonRepository {
    public IEnumerable<Person> Search(*** SOME_METHOD_SIGNATURE ***);
}

Twoja aplikacja konsumencka naprawdę nie dba o konkretną implementację. To po prostu chwyci poprawną konkretną implementację z Unity / Ninject i rozpocznie wyszukiwanie.

IPersonRespository repo = GetConcreteImplementationFromConfig();
repo.Search( ... );

What I ' m zastanawiasz się, czego byś użył do podpisu metody, która jest zarówno elastyczna, jak i rozszerzalna niezależnie od implementacji.

Wariant 1.

public IEnumerable<Person> Search(Expression<Func<Person, bool>> expression);

To miłe, ponieważ jeśli używasz kontekstu danych obsługującego LINQ (np. EntityFramework), możesz po prostu przekazać wyrażenie bezpośrednio do swojego kontekstu. Opcja ta wydaje się jednak spadać, jeśli implementacja wymaga użycia ręcznie przygotowanych przechowywanych procs/sql / ADO.NET itp...

Opcja 2.

public IEnumerable<Person> Search(PersonSearch parameters);

public class PersonSearch {
    public int? Age {get;set;}
    public string FullName {get;set;}
    public string PartialName { get; set; }
}

Ten wydaje się najbardziej elastyczny (w tym sensie, że będzie działał z Linq, zwykłym starym SQL.

Ale po prostu śmierdzi "pisaniem własnego języka zapytań", ponieważ musisz uwzględnić każde możliwe zapytanie, które konsument może chcieć zrobić. np. Wiek > = 18 & & wiek

Wariant 3.

Są jakieś inne opcje ?

Author: smartcaveman, 2011-10-26

3 answers

  1. Twoje stwierdzenie, że "opcja 1 wydaje się spaść choć jeśli implementacja ma używać ręcznie wykonane przechowywane procs/sql / ADO.NET etc" jest nieprawidłowe. Jest całkowicie możliwe wygenerowanie modelu podobnego do tego w opcji 2, poprzez interpretację wyrażenia zapytania z implementacją ExpressionVisitor.

  2. Jeśli zamierzasz zwrócić IEnumerable<T> zamiast IQueryable<T> z metody LINQ Search, powinieneś włączyć jakiś mechanizm do obsługi (1) stronicowanie oraz (2) sortowanie.

  3. Naprawdę nie lubię opcji # 2. Nie wyjaśnia, czego szukasz. na przykład, jeśli wiek jest null, czy to oznacza, że szukasz użytkowników z null wieku, lub ignorujesz parametr wieku? Co się stanie, jeśli podano nazwę i nazwę częściową? Dzięki podejściu LINQ możesz robić takie rzeczy jak StartsWith, Contains, Equals, itd.

  4. Implementacja wzorca repozytorium ma za zadanie logiki dostępu do danych i udostępnienia zorientowanego na biznes interfejsu. Poprzez bezpośredni dostęp do repozytorium za pomocą ogólnego interfejsu (Expression<Func<Person,bool>>), tracisz na tym trochę, ponieważ interfejs nie przekazuje intencji do wywołującego. Istnieje kilka sposobów, aby to zrobić lepiej.

    • implementacja wzorca specyfikacji bazującego na wyrażeniach LINQ tworzy bardziej silnie wpisywane zapytania. Tak więc, zamiast zapytań dla dorosłych przez Search(person => person.Age.HasValue && person.Age.Value > 18), użyjesz składni specyfikacji podobnie jak Search(new PersonIsAdultSpecification());, gdzie Specyfikacja zawiera podstawowe wyrażenie LINQ, ale wyświetla interfejs zorientowany na biznes.
        Osobiście podoba mi się to podejście, ale Ayende nazywa je "architekturą w otchłani zagłady", ponieważ łatwo może prowadzić do nadmiernej inżynierii. Jego alternatywną sugestią jest owinięcie konkretnych zapytań w metody rozszerzeń. Myślę, że jest to prawdopodobnie równie realne, ale wolę mieć obiekt silnie wpisany.
    • najprostszym sposobem, aby to zrobić byłoby zadeklarowanie realistycznych zapytań, które wykonasz w interfejsie IPersonRepository. Tak więc interfejs deklaruje metodę SearchForAdults().

Ogólnie , za każdym razem, gdy odpytywasz bazę danych, powinieneś celowo próbować uzyskać określone dane. Za każdym razem, gdy zapytujesz repozytorium, powinieneś celowo próbować uzyskać obiekty biznesowe, które spełniają określone ograniczenia biznesowe. Dlaczego nie uczynić tego ograniczenia biznesowego wyraźniej? Zdefiniowanie dobrego interfejsu dla każdej usługi zależy od konsumenta tej usługi. Nie ma ogólnego interfejsu repozytorium magic bullet, ale możesz utworzyć lepszy lub gorszy w kontekście konkretnej aplikacji. Chodzi o to, aby pamiętać, dlaczego w ogóle używasz wzorca repozytorium, a to jest uproszczenie logiki i stworzenie bardziej intuicyjnego punktu dostępu.

 6
Author: smartcaveman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-13 16:44:32

Nie jestem pewien, czy w tym przypadku istnieje coś takiego jak "oficjalne źródło", ale prowadziłem projekt LINQ dla Visual Basica, więc może to się liczy. W każdym razie oto moje zdanie na ten temat:

Problem, który wydajesz się stwarzać, polega na tym, że chcesz być w stanie przesłać wyrażenie predykatu do backendu "niezależnie od implementacji [backend]."Wydaje mi się jednak, że dwie opcje, które wymieniasz, są naprawdę tylko wariantami siebie nawzajem, ponieważ w obu przypadkach backend musi być w stanie zrozumieć przekazywaną specyfikację predykatów. Opcja pierwsza jest bardziej ogólna i elastyczna niż opcja druga (ale jest to również dużo więcej bólu do przetłumaczenia). Opcja druga jest bardziej ograniczona niż opcja druga, ale jest prostszą strukturą do obsługi (chociaż ponieważ pozwala się na określenie coraz bardziej złożonych zapytań w drugiej opcji, struktura nieuchronnie będzie coraz bardziej przypominać strukturę z pierwszej opcji).

Najważniejsze jest że nie ma dobrej odpowiedzi na twoje pytanie. Nie ma uniwersalnej struktury wyrażeń, którą można mieć pewność, że wszyscy dostawcy będą wspierać, więc jeśli naprawdę chcesz mieć metodę wyszukiwania, która działa nad dowolnym dostawcą niezależnie od implementacji, musisz być skłonny wrócić do uruchamiania predykatu lokalnie, jeśli dostawca nie obsługuje, powiedzmy, drzew wyrażeń. W zależności od scenariusza, może to lub nie zadziała dla Ciebie.

Z drugiej strony, jeśli (jak jest bardziej prawdopodobnie) lista możliwych dostawców, których otrzymasz, jest ograniczona, prawdopodobnie lepiej będzie zdecydować, co jest dla Ciebie najważniejsze (łatwość użycia/ekspresywność vs łatwość wdrożenia) i wybrać, jakie ograniczenia chcesz umieścić na dostawcach. Drzewa wyrażeń byłyby z pewnością najładniejsze pod wieloma względami dla programisty aplikacji konsumenckich, więc jeśli uważasz, że możesz uciec z tym ograniczeniem, śmiało. W przeciwnym razie jakaś prostsza struktura niestandardowa prawdopodobnie spowoduje, że mniej ból i ból serca. (Nie polecam próbować tłumaczyć drzewa ekspresji samodzielnie, chyba że masz dużo dodatkowego czasu i cierpliwości.)

 1
Author: panopticoncentral,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-11 22:33:53

Jedną z rzeczy, która zawsze mnie niepokoiła o wzór repozytorium, jest nieelastyczność, jeśli chodzi o rzeczy takie jak grupowanie, agregowanie liczników i tak dalej. IExtendedRepository), który jest wyposażony w podpis metody funcy, taki jak ten:

TResult Aggregate<TResult>(Func<IQueryable<TEntity>, TResult> aggregatorFunc);

Ta metoda jest trochę potwornym narzędziem, które można wykorzystać na wiele ciekawych sposobów:

var countSomething = repository.Aggregate(x => x.Where(y => y.SomeProperty).Count());

Zauważ, że (w zależności od implementacji repozytorium) wywołanie Count () będzie skompilować do liczby SQL, więc nie jest to liczba w pamięci ().

 1
Author: Christoph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-13 15:06:20