Metody repozytorium a Rozszerzanie IQueryable

Mam repozytoria (np. ContactRepository, UserRepository itd.), które zawierają dostęp do danych w modelu domeny.

Kiedy patrzyłem na szukając danych , np.

  • znalezienie kontaktu, którego imię zaczyna się od xyz
  • Kontakt, którego urodziny są po 1960

    (etc),

Zacząłem implementować metody repozytorium takie jak FirstNameStartsWith (string prefix) i YoungerThanBirthYear (int year), zasadniczo podążając za wieloma przykładami.

Potem pojawia się problem - co zrobić, jeśli muszę połączyć kilka wyszukiwań? Każda z moich metod wyszukiwania repozytoriów, jak powyżej, zwraca tylko skończony zestaw rzeczywistych obiektów domeny. W poszukiwaniu lepszego sposobu zacząłem pisać metody rozszerzenia na IQueryable, np. to:

public static IQueryable<Contact> FirstNameStartsWith(
               this IQueryable<Contact> contacts, String prefix)
{
    return contacts.Where(
        contact => contact.FirstName.StartsWith(prefix));
}        

Teraz mogę robić takie rzeczy jak

ContactRepository.GetAll().FirstNameStartsWith("tex").YoungerThanBirthYear(1960);

Jednak znalazłem się pisząc metody rozszerzeń (i wymyślanie szalonych klas, takich jak ContactsQueryableExtensions wszędzie i tracę "ładne grupowanie" mając wszystko w odpowiednim repozytorium.

Czy to jest naprawdę sposób, aby to zrobić, czy jest lepszy sposób, aby osiągnąć ten sam cel?

Author: svick, 2009-09-13

4 answers

@Alex - wiem, że to stare pytanie, ale to, co bym zrobił, to pozwolić repozytorium robić naprawdę proste rzeczy tylko. Oznacza to pobranie wszystkich rekordów dla tabeli lub widoku.

Następnie w warstwie usług (używasz rozwiązania n-warstwowego, prawda? :)) zajmowałbym się tam wszystkimi "specjalnymi" pytaniami.

Ok, przykładowy czas.

Warstwa Repozytorium

ContactRepository.cs

public IQueryable<Contact> GetContacts()
{
    return (from q in SqlContext.Contacts
            select q).AsQueryable();
}
Ładnie i prosto. SqlContext jest instancją twojego EF Context .. który ma na sobie Entity nazwane Contacts .. czyli w zasadzie twoja klasa kontaktów sql.

Oznacza to, że ta metoda w zasadzie działa: SELECT * FROM CONTACTS... ale to nie jest trafienie do bazy danych z tym zapytaniem .. to tylko pytanie.

Ok .. następna warstwa.. KICK ... up we go (Inception anyone?)

Warstwa Usług

ContactService.cs

public  ICollection<Contact> FindContacts(string name)
{
    return FindContacts(name, null)
}

public ICollection<Contact> FindContacts(string name, int? year)
{
   IQueryable<Contact> query = _contactRepository.GetContacts();

   if (!string.IsNullOrEmpty(name))
   {
       query = from q in query
               where q.FirstName.StartsWith(name)
               select q;
   }

   if (int.HasValue)
   {
       query = from q in query
               where q.Birthday.Year <= year.Value
               select q);
    }

    return (from q in query
            select q).ToList();
}
Zrobione. Więc podsumujmy. Najpierw zaczynamy od prostego zapytania " Get everything from contacts ". Jeśli mamy nazwisko pod warunkiem, pozwala dodać filtr do filtrowania wszystkich kontaktów po nazwie. Następnie, jeśli mamy podany rok, filtrujemy urodziny według roku. Itd. Na koniec, następnie uderzamy w DB (z tym zmodyfikowanym zapytaniem) i zobaczymy, jakie wyniki otrzymamy.

Uwagi: -

  • pominąłem wszelkie iniekcje zależności dla uproszczenia. To więcej niż zalecane.
  • to wszystko pseduo-code. Untested (przeciwko kompilatorowi), ale masz pomysł ....

Na wynos punkty

  • warstwa usług obsługuje wszystkie spryt. To właśnie tam decydujesz, jakich danych potrzebujesz.
  • repozytorium jest prostym SELECT * FROM TABLE lub prostym INSERT/UPDATE into TABLE.

Powodzenia:)

 6
Author: Pure.Krome,
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
2010-08-02 01:25:18

Myślałem o tym dużo ostatnio, po rozpoczęciu mojej obecnej pracy. Jestem przyzwyczajony do repozytoriów, idą pełną ścieżką IQueryable używając tylko repozytoriów bare bones, jak sugerujesz.

Czuję, że wzorzec repo jest dźwiękowy i wykonuje półefektywną pracę w opisywaniu, w jaki sposób chcesz pracować z danymi w domenie aplikacji. Jednak problem, który opisujesz, zdecydowanie występuje. Robi się bałagan, szybko, poza prostą aplikacją.

Czy są jakieś sposoby na zastanów się, dlaczego prosisz o dane na tak wiele sposobów? Jeśli nie, naprawdę uważam, że podejście hybrydowe jest najlepszym sposobem. Twórz metody repo dla rzeczy, które używasz ponownie. Rzeczy, które mają sens. Suche i takie tam. Ale te jednorazowe? Dlaczego nie skorzystać z IQueryable i seksownych rzeczy, które możesz z nim zrobić? To głupie, jak powiedziałeś, stworzyć metodę do tego, ale to nie znaczy, że nie potrzebujesz danych. Suchy nie ma zastosowania, prawda?

It would take dyscyplina, aby to zrobić dobrze, ale naprawdę uważam, że to właściwa droga.

 12
Author: Chad Ruppert,
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
2009-09-13 01:20:43

Zdaję sobie sprawę, że to stare, ale ostatnio zajmowałem się tym samym problemem i doszedłem do tego samego wniosku co Chad: przy odrobinie dyscypliny, hybryda metod rozszerzeń i metod repozytoriów wydaje się działać najlepiej.

Niektóre ogólne zasady, których przestrzegam w mojej aplikacji (Entity Framework):

Zamawianie zapytań

Jeśli metoda jest używana tylko do zamawiania, wolę pisać metody rozszerzeń, które działają na IQueryable<T> lub IOrderedQueryable<T> (aby wykorzystać bazową dostawca.) np.

public static IOrderedQueryable<TermRegistration> ThenByStudentName(
    this IOrderedQueryable<TermRegistration> query)
{
    return query
        .ThenBy(reg => reg.Student.FamilyName)
        .ThenBy(reg => reg.Student.GivenName);
}

TERAZ MOGĘ używać ThenByStudentName() w mojej klasie repozytorium.

Zapytania zwracające pojedyncze instancje

Jeśli metoda polega na zapytaniu prymitywnymi parametrami, zwykle wymaga ObjectContext i nie może być łatwo wykonana static. Te metody pozostawiam w moim repozytorium, np.

public Student GetById(int id)
{
    // Calls context.ObjectSet<T>().SingleOrDefault(predicate) 
    // on my generic EntityRepository<T> class 
    return SingleOrDefault(student => student.Active && student.Id == id);
}

Jeśli jednak metoda polega na zapytaniu EntityObject przy użyciu jej właściwości nawigacji , zwykle można ją wykonać static dość łatwo i zaimplementowane jako metoda rozszerzenia. np.

public static TermRegistration GetLatestRegistration(this Student student)
{
    return student.TermRegistrations.AsQueryable()
        .OrderByTerm()
        .FirstOrDefault();
}

Teraz mogę wygodnie pisać someStudent.GetLatestRegistration() bez potrzeby użycia instancji repozytorium w bieżącym zakresie.

Zapytania zwracające zbiory

Jeśli metoda zwróci jakieś IEnumerable, ICollection lub IList, wtedy lubię go static, jeśli to możliwe, i pozostawić go w repozytorium , nawet jeśli używa właściwości nawigacji. np.

public static IList<TermRegistration> GetByTerm(Term term, bool ordered)
{
    var termReg = term.TermRegistrations;
    return (ordered)
        ? termReg.AsQueryable().OrderByStudentName().ToList()
        : termReg.ToList();
}

To dlatego, że moje GetAll() metody już działa w repozytorium i pomaga uniknąć bałaganu metod rozszerzeń.

Innym powodem, dla którego Nie zaimplementowano tych "getterów kolekcji" jako metod rozszerzeń, jest to, że wymagają one bardziej szczegółowego nazewnictwa, aby miało znaczenie, ponieważ Typ zwracania nie jest sugerowany. Na przykład, ostatnim przykładem będzie GetTermRegistrationsByTerm(this Term term).

Mam nadzieję, że to pomoże!

 4
Author: Rob,
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
2012-02-15 17:42:28

Sześć lat później jestem pewien, że @ Alex rozwiązał swój problem, ale po przeczytaniu zaakceptowanej odpowiedzi chciałem dodać moje dwa grosze.

Ogólny cel rozszerzenia IQueryable kolekcji w repozytorium w celu zapewnienia elastyczności i umożliwienia konsumentom dostosowywania wyszukiwania danych. To, co Alex już zrobił, to dobra robota.

Podstawową rolą warstwy usług jest przestrzeganie zasady rozdzielenia obaw i logiki adresowej związane z funkcją biznesową.

W rzeczywistych aplikacjach, logika zapytań często nie wymaga rozszerzenia poza mechanikę pobierania dostarczaną przez samo repozytorium (np. zmiany wartości, konwersje typu).

Rozważmy dwa następujące scenariusze:

IQueryable<Vehicle> Vehicles { get; }

// raw data
public static IQueryable<Vehicle> OwnedBy(this IQueryable<Vehicle> query, int ownerId)
{
    return query.Where(v => v.OwnerId == ownerId);
}

// business purpose
public static IQueryable<Vehicle> UsedThisYear(this IQueryable<Vehicle> query)
{
    return query.Where(v => v.LastUsed.Year == DateTime.Now.Year);
}

Obie metody są prostymi rozszerzeniami zapytań, ale jakkolwiek subtelne, mają różne role. Pierwszy jest prostym filtrem, podczas gdy drugi oznacza potrzebę biznesową (np. konserwacja lub fakturowanie). W prostym aplikację można zaimplementować w repozytorium. W bardziej idealistycznym systemie {[2] } najlepiej nadaje się do warstwy usług (i może być nawet zaimplementowany jako normalna metoda instancji), gdzie może również lepiej ułatwić strategię CQRS oddzielania poleceń i zapytań .

Kluczowe kwestie to (a) główny cel Twojego repozytorium oraz (b) jak bardzo lubisz stosować się do filozofii CQR i DDD.

 0
Author: one.beat.consumer,
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
2016-03-10 19:44:38