Frage

I haben Repositories (z.B. ContactRepository, UserRepository usw.), der Datenzugriff auf das Domänenmodell einzukapseln.

Als ich in war auf der Suche Suche nach Daten , z.B.

  • die Suche nach einem Kontakt, dessen Vorname beginnt mit XYZ
  • ein Kontakt, dessen Geburtstag nach 1960

    (etc),

begann ich Repository Methoden wie FirstNameStartsWith (string prefix) und YoungerThanBirthYear (int Jahr) Umsetzung im Grunde nach den vielen Beispielen gibt.

Dann traf ich ein Problem - was ist, wenn ich mehrere Suchen kombinieren? Jedes meiner Repository Suchmethoden, wie oben, gibt nur eine endliche Menge von tatsächlichen Domain-Objekten. Auf der Suche nach einem besseren Weg, begann ich Erweiterungsmethoden auf IQueryable zu schreiben, zum Beispiel folgt aus:

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

Jetzt kann ich Dinge tun, wie

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

Allerdings fand ich mich Erweiterungsmethoden zu schreiben (und verrückt Klassen wie ContactsQueryableExtensions die ganzen erfinden, und ich verliere die „schöne Gruppierung“ von allem in dem entsprechenden Repository mit.

Ist das wirklich die Art und Weise, es zu tun, oder gibt es einen besseren Weg, um das gleiche Ziel zu erreichen?

War es hilfreich?

Lösung

@ Alex - ich weiß, das ist eine alte Frage, aber was ich würde tun das Repository tun zu lassen wirklich einfache Sachen nur. Diese Mittel, erhalten alle Datensätze für eine Tabelle oder Sicht.

Dann wird in der Schicht SERVICES (Sie eine n-tiered Lösung verwenden, oder? :)) ich das alles würde Umgang mit ‚spezieller‘ Abfrage Sachen gibt.

Ok, zB Zeit.

Repository-Schicht

ContactRepository.cs

public IQueryable<Contact> GetContacts()
{
    return (from q in SqlContext.Contacts
            select q).AsQueryable();
}

Schön und einfach. SqlContext ist die Instanz Ihrer EF Context .., die eine Entity hat sie aufgefordert hatte Contacts .. das ist im Grunde Klasse SQL-Kontakte ist.

Das heißt, im Grunde diese Methode tut: SELECT * FROM CONTACTS ... aber es ist nicht die Datenbank mit dieser Abfrage schlagen .. es ist nur eine Abfrage jetzt

.

Ok .. nächste Schicht .. KICK ... nach oben gehen wir ( Inception anyone?)

Services Layer

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();
}

Fertig.

So können rekapitulieren. Zuerst starten wir unsere mit einem einfachen ‚ Holen Sie alles aus Kontakte ‘ query. Wenn wir nun einen Namen zur Verfügung gestellt haben, können Sie einen Filter hinzufügen, alle Kontakte filtern nach Namen. Als nächstes, wenn wir ein Jahr zur Verfügung gestellt haben, dann filtern wir den Geburtstag von Jahr. Usw. Schließlich treffen wir dann die DB (mit dieser modifizierten Abfrage) und sehen, welche Ergebnisse wir zurück.

HINWEISE: -

  • Ich habe jede Dependency Injection der Einfachheit halber weggelassen. Es ist mehr als dringend empfohlen.
  • Das ist alles pseduo-Code. Ungeprüfte (gegen einen Compiler), aber Sie bekommen die Idee ....

Mitnehm-Punkte

  • Die Services-Ebene behandelt alle Smarts. Das ist, wo Sie entscheiden, welche Daten Sie benötigen.
  • Das Repository ist eine einfache SELECT * FROM TABLE oder eine einfache INSERT / UPDATE in Tabelle.

Viel Glück:)

Andere Tipps

Ich habe in letzter Zeit viel darüber nachgedacht, nachdem in meinem derzeitigen Job zu starten. Ich bin zu Repositorys verwendet, gehen sie den vollständigen Pfad IQueryable nur nackten Knochen Repositories wie Sie vorschlagen.

Ich fühle mich das Repo-Muster Ton und macht eine halb effektive Arbeit zu beschreiben, wie Sie mit den Daten in der Anwendungsdomäne arbeiten mögen. Doch das Problem, das Sie beschreiben, auf jeden Fall auftritt. Es wird chaotisch, schnell, über eine einfache Anwendung.

Gibt es vielleicht Möglichkeiten zu überdenken, warum Sie in so viele Möglichkeiten, für die Daten fragen? Wenn nicht, ich fühle mich wirklich, dass ein Hybrid-Ansatz ist der beste Weg zu gehen. Erstellen Sie Repo-Methoden für das Material, das Sie wiederverwenden. Sachen, die es tatsächlich Sinn macht für. DRY und das alles. Aber diese one-offs? Warum nutzen Sie nicht von IQueryable und die sexy Dinge kann man damit machen? Es ist dumm, wie Sie gesagt haben, ein Verfahren für das zu schaffen, aber es bedeutet nicht, dass Sie die Daten nicht benötigen. DRY nicht wirklich anwenden es nicht wahr?

Es wäre Disziplin nimmt dies auch zu tun, aber ich glaube wirklich, es ist ein geeigneter Weg.

Ich weiß, das ist alt, aber ich habe in letzter Zeit mit dem gleichen Problem beschäftigt, und ich kam zu dem gleichen Schluss wie Tschad: mit einer wenig Disziplin, ein Hybrid aus Erweiterungsmethoden und Repository-Methoden scheint zu funktionieren am besten <. / p>

Einige allgemeine Regeln, die ich in meinem (Entity Framework) Anwendung verfolgt haben:

Bestellung Abfragen

Wenn die Methode nur für die Bestellung verwendet wird, ziehe ich Erweiterungsmethoden zu schreiben, die auf IQueryable<T> oder IOrderedQueryable<T> betreiben (den zugrunde liegenden Anbieter zu nutzen.) z.

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

Jetzt kann ich ThenByStudentName() wie in meiner Repository-Klasse benötigt wird.

Abfragen Rückkehr einzelne Instanzen

Wenn die Methode durch primitive Parameter abfragt beinhaltet, es erfordert in der Regel eine ObjectContext und nicht leicht static gemacht werden kann. Diese Methoden, die ich auf meinem Repository verlassen, z.

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);
}

Wenn jedoch das Verfahren beinhaltet stattdessen eine EntityObject mit seinen Navigationseigenschaften abfragen , es kann in der Regel recht einfach gemacht static wird und als Erweiterung Methode implementiert. z.

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

Jetzt kann ich bequem schreiben someStudent.GetLatestRegistration() ohne eine Repository-Instanz im aktuellen Bereich zu benötigen.

Abfragen Rückkehr Sammlungen

Wenn die Methode einige IEnumerable, ICollection oder IList gibt, dann möchte ich es machen static wenn möglich, und lassen Sie es auf das Repository auch wenn es Navigationseigenschaften verwendet. zB

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

Das ist, weil meine GetAll() Methoden bereits auf dem Repository leben, und es hilft, ein unübersichtlich Durcheinander von Erweiterungsmethoden zu vermeiden.

Ein weiterer Grund für die nicht diese „Sammlung Getter“ als Erweiterungsmethoden der Umsetzung ist, dass sie würde ausführlichere Namensgebung erfordern sinnvoll zu sein, da der Rückgabetyp nicht impliziert. Zum Beispiel würde das letzte Beispiel GetTermRegistrationsByTerm(this Term term) werden.

Ich hoffe, das hilft!

Sechs Jahre später, ich bin sicher @ Alex sein Problem gelöst hat, aber nach der akzeptierten Antwort lesen wollte ich meinen zwei Cent hinzuzufügen.

Der allgemeine Zweck der Verlängerung IQueryable Sammlungen in einem Repository Flexibilität zu bieten und die Verbraucher der Lage versetzen, den Datenabruf anpassen. Was Alex hat bereits gute Arbeit geleistet.

Die primäre Rolle eines Service-Layer ist auf die Trennung von Bedenken Prinzip und Adresse Befehlslogik im Zusammenhang mit Business-Funktion zu halten.

In realen Anwendungen, Abfragelogik braucht oft keine Erweiterung über die Abruf Mechanik vom Repository bereitgestellt selbst (ex. Wertänderungen, Typkonvertierungen).

Beachten Sie die beiden folgenden Szenarien:

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);
}

Beide Methoden sind einfache Abfrage-Erweiterungen, aber wie subtil sie haben unterschiedliche Rollen. Der erste ist ein einfacher Filter, während das zweite Geschäftsanforderungen impliziert (ex. Wartung oder Abrechnung). In einer einfachen Anwendung könnte man sie beide in einem Repository implementieren. In einem idealistischen System UsedThisYear ist am besten geeignet für die Service-Schicht (und sogar als normale Instanzmethode implementiert werden kann), wo sie erleichtern auch besser können CQRS Strategie der Trennung von Befehlen und Abfragen .

Wichtige Überlegungen sind (a) der primäre Zweck des Repository und (b) wie viel Sie auf halten mögen CQRS und DDD Philosophien.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top