2010-11-28 20 views
2

Ich arbeite an der Integration einiger gemeinsamer Repository-Infrastruktur in einige Anwendungen, die EF, L2SQL und WCF Data Services umschließen (obwohl die zugrunde liegenden Data Access-Implementierungen beliebig sein sollten). Ich habe etwas darüber gelesen, aber ich finde kein Beispiel, das wirklich befriedigt.Generic Repository

Ich begann mit:

public interface IRepository : IDisposable 
{ 
    IQueryable<T> Query<T>(); 
    void Attach(object entity); 
    void ForDeletion(object entity); 
    void SaveChanges(); 
} 

Aber Ich mag die Idee eines Endlagers mit schmalen Domain Verträge (http://codebetter.com/blogs/gregyoung/archive/2009/01/16/ddd-the-generic-repository.aspx). Das oben Genannte verlässt den Benutzer, um alle Entitätstypen zu kennen, die vom Repository unterstützt werden.

Ich werde nicht sagen, dass es nicht in Frage kommt, aber ich wäre sehr schwer, überzeugt zu sein, dass IQueryables selbst nicht Teil des Repository-Vertrags sein sollte. Ich bin kein Fan von Kesselplatten-Code und ich glaube fest, dass je mehr Sie haben, desto mehr Wartung schwarze Löcher, die Sie einführen. Also, was ich sage ist, dass es sehr schwer sein würde, mir das alles zu überzeugen, die wie folgt aussieht:

Public IEnumerable<Customer> GetCustomersWithFirstNameOf(string _Name) { 
    internalGenericRepository.FetchByQueryObject(new CustomerFirstNameOfQuery(_Name)); //could be hql or whatever 
} 

ist alles andere als ein völlig abgründigen Idee. Wie wäre es, wenn Sie nach dem Nachnamen und Nachnamen suchen möchten. Oder Vorname oder Nachname ... etc. Sie haben am Ende ein Repository mit mehr als 1000 Operationen, von denen die Hälfte dieselbe Logik wiederholt. Hinweis: Ich bin nicht der Aufruf Code sollte für die Anwendung alle Filterung und so verantwortlich sein, sondern vielmehr, dass eine ergänzende Spezifikation Quelle macht Sinn für mich:

public static class CustomerSpecifications 
{ 
    public IQueryable<Customer> WithActiveSubscriptions(this IQueryable<Customer> customers, DateTime? start, DateTime? end) 
    { 
     // expression manipulation 
     return customers; 
    } 
} 


// bind some data source 
repository.GetQueryable().WithActiveSubscriptions(); 

Ok, so vorwärts bewegt, ich denke, die Domain-Modell explizite Repositorys klingt wie eine gute Idee in folgendem Format:

public interface IRepository : IDisposable 
{ 
    void SaveChanges(); 
} 

public interface IRepository<T>: IRepository 
{ 
    IQueryable<T> GetQueryable(); 
    void Attach(T entity); 
    void ForDeletion(T entity); 
} 

dann

public class CustomerRepository:IRepository<Customer> 
{ 
    private ObjectContext _context; 
    // trivial implementation 
} 

aber mein Problem dabei ist, dass es erlaubt mir nur delet e Kunden. Was ist mit dem Fall, in dem ich die Adresse eines Kunden löschen möchte? Das heißt, ich verwende das Repository, um eine Customer-Entität abzufragen, möchte aber dann myCustomer.CustomerAddresses [0] löschen. Ich muss ein zweites Repository erstellen, um die gewünschte Adresse anzuhängen und zu löschen.

Ich glaube, ich meine CustomerRepository sein könnte:

public class CustomerRepository:IRepository<Customer>, IRepository<CustomerAddress> 
{ 
    private ObjectContext _context; 
    // trivial implementation 
} 

, die mir das Repository wiederverwenden lassen würde CustomerAddresses zu löschen, aber ich bin nicht sicher, wie ich über Vererbungs fühlen IRepository<T> für jeden Teil des Graphen I löschen wollen für ...

Wer hat irgendwelche Vorschläge für eine bessere Umsetzung?

Antwort

2

Viele Fragen drin, und einige der Antworten könnten subjektiv/opioniert sein, aber ich werde mit den Schlägen rollen.

IQueryable vs spezifische Methoden.

Ich stimme dir tatsächlich zu.Ich mag die Idee nicht, viele und viele Methoden zu haben, um alle verschiedenen Operationen in meinem Repository zu definieren. Ich bevorzuge es, dass der aufrufende Code die Spezifikation liefert. Jetzt ist mein Anrufcode nicht meine Präsentation - es ist ein Domain Service/BLL. Es ist also nicht so, dass wir der Benutzeroberfläche die volle Leistung geben, sondern dass unser Repository nur sehr allgemein bleibt. DDD-Puristen würden argumentieren, dass Repositories Abstraktionen Ihres Domänenmodells sind und daher bestimmte Domänenvorgänge unterstützen sollten. Ich lasse das offen für Sie, um zu entscheiden. Auch wenn Sie IQueryable verwenden, dann erzwingen Sie die Verwendung eines "unbekannten" LINQ-Anbieters. Kein Problem für mich, da ich immer etwas benutze, das von LINQ (Entity Framework, zum Beispiel) ableitet. Aber das ist ein anderes Problem, das DDD-Puristen haben.

Was ist, wenn Sie auf Vornamen suchen möchten und Nachnamen

Nun, ich benutze Expression<Func<T,bool>> für meine Domain-Services, also kann ich dies tun:

var customers = repository.FindAll<Customer>(x => x.FirstName == "Bob" && x.LastName == "Saget"); 

Wenn Sie mag das nicht, Sie könnten das Spezifikation Muster verwenden, um AND/OR-Code zu verwenden.

löschen Kundenadresse

Nun allgemein gesprochen, sollten Sie ein Repository pro Aggregat Wurzel haben. Wenn Sie Ihr Domänenmodell nicht kennen, klingt es wie "Kunde" ist ein aggregierter Stamm und "Kundenadresse" ist ein Aggregat, das immer einem "Kunden" zugeordnet ist (z. B. kann nicht ohne einen existieren).

Daher benötigen Sie kein CustomerAddressRepository. CustomerAddress-Vorgänge sollten über Ihr CustomerAddress-Repository durchgeführt werden.

diesem Grund habe ich eine GenericRepository<T> Implementierung von IRepository<T> erstellen möchten, die das Kerngeschäft auf dem Aggregat implementiert (Finden, Hinzufügen, Entfernen), dann noch spezifischere Implementierungen von GenericRepository<T> abzuleiten.

So würde ich Customer Repository handhaben. Erstellen Sie eine GenericRepository<T>, die IRepository<T> Kernoperationen implementiert. Erstellen Sie dann eine weitere Schnittstelle für ICustomerRepository, die auch IRepository<T> implementiert.

Erstellen Sie jetzt eine Implementierung mit dem Namen CustomerRepository, die die Implementierung des Kernrepositorys von GenericRepository<T> erbt und diese auch erweitert, um zusätzliche Operationen (wie das Entfernen von Adressen) zu ermöglichen.

public class CustomerRepository : GenericRepository<Customer>, ICustomerRepository 
{ 
    // inherits, for example: 
    // IQueryable<Customer> Query<Customer>(); 

    public void Remove(Address address) 
    { 
     // get the customer (if you havent already got it) 
     var cust = ctx.Customers.SingleOrDefault(x => x.CustId == address.CustId); 
     cust.Addresses.Remove(address); 
    } 
} 

Und Ihre ICustomerRepository würde wie folgt aussehen:

public interface ICustomerRepository : GenericRepository<Customer>, IRepository<Customer> 
{ 
    // inherited from GenericRepository<Customer> 
    //IQueryable<T> Query<T>(); 
    //void Attach(object entity); 
    //void ForDeletion(object entity); 
    //void SaveChanges(); 
    void Remove(Address address); 
} 

wissen, was ich meine? Beginnen Sie mit wirklich generischen Repositories und erhalten Sie dann spezifischer, wie Sie es benötigen.

Sie benötigen kein CustomerAddressRepository. Führen Sie die Operationen über das CustomerRepository aus, wie oben gezeigt.

HTH.

+0

Sie haben also ein GenericRepository pro Aggregat root ... wie in keinem GenericRepository für CustomerAddress? – Jeff

+0

@ JeffN825. Richtig. Versuchen Sie, nicht in die Denkweise von "Ich brauche ein Repository pro Entität" einzudringen. Denken Sie darüber nach, * wie * Ihre Entitäten abgerufen werden. Können Sie jemals eine Kundenadresse abrufen, ohne einen Kunden zu haben? Wahrscheinlich nicht - daher sollten Sie kein CustomerAddress-Repository haben. – RPM1984

+0

@ JeffN825 - und auch, erinnere dich 'GenericRepository ' verwendet Generika, so dass es nur 1 Datei gibt. Es wird injiziert (über DI), wenn der Code eine bestimmte Schnittstelle anfordert. In Ihrem Beispiel geben Sie 'GenericRepository ' '' CustomerRepository' (das 'GenericRepository ' erbt) – RPM1984