Wir entwickeln eine ASP.NET MVC-Anwendung und erstellen jetzt die Repository- / Serviceklassen. Ich frage mich, ob die Erstellung einer generischen IRepository-Schnittstelle, die von allen Repositorys implementiert wird, wesentliche Vorteile bietet, während jedes Repository über eine eigene Schnittstelle und einen eigenen Methodensatz verfügt.
Beispiel: Eine generische IRepository-Schnittstelle könnte folgendermaßen aussehen (aus dieser Antwort entnommen ):
public interface IRepository : IDisposable
{
T[] GetAll<T>();
T[] GetAll<T>(Expression<Func<T, bool>> filter);
T GetSingle<T>(Expression<Func<T, bool>> filter);
T GetSingle<T>(Expression<Func<T, bool>> filter, List<Expression<Func<T, object>>> subSelectors);
void Delete<T>(T entity);
void Add<T>(T entity);
int SaveChanges();
DbTransaction BeginTransaction();
}
Jedes Repository würde diese Schnittstelle implementieren, zum Beispiel:
- CustomerRepository: IRepository
- ProductRepository: IRepository
- etc.
Die Alternative, der wir in früheren Projekten gefolgt sind, wäre:
public interface IInvoiceRepository : IDisposable
{
EntityCollection<InvoiceEntity> GetAllInvoices(int accountId);
EntityCollection<InvoiceEntity> GetAllInvoices(DateTime theDate);
InvoiceEntity GetSingleInvoice(int id, bool doFetchRelated);
InvoiceEntity GetSingleInvoice(DateTime invoiceDate, int accountId); //unique
InvoiceEntity CreateInvoice();
InvoiceLineEntity CreateInvoiceLine();
void SaveChanges(InvoiceEntity); //handles inserts or updates
void DeleteInvoice(InvoiceEntity);
void DeleteInvoiceLine(InvoiceLineEntity);
}
Im zweiten Fall wären die Ausdrücke (LINQ oder anderweitig) vollständig in der Repository-Implementierung enthalten. Wer auch immer den Service implementiert, muss nur wissen, welche Repository-Funktion aufgerufen werden soll.
Ich glaube, ich sehe nicht den Vorteil, die gesamte Ausdruckssyntax in die Serviceklasse zu schreiben und an das Repository zu übergeben. Würde dies nicht bedeuten, dass in vielen Fällen leicht zu verwirrender LINQ-Code dupliziert wird?
In unserem alten Rechnungsstellungssystem rufen wir beispielsweise an
InvoiceRepository.GetSingleInvoice(DateTime invoiceDate, int accountId)
von einigen verschiedenen Diensten (Kunde, Rechnung, Konto usw.). Das scheint viel sauberer zu sein, als an mehreren Stellen Folgendes zu schreiben:
rep.GetSingle(x => x.AccountId = someId && x.InvoiceDate = someDate.Date);
Der einzige Nachteil, den ich bei der Verwendung des spezifischen Ansatzes sehe, besteht darin, dass wir möglicherweise viele Permutationen von Get * -Funktionen erhalten, dies scheint jedoch immer noch vorzuziehen, um die Ausdruckslogik in die Service-Klassen zu verschieben.
Was vermisse ich?
quelle
Antworten:
Dies ist ein Problem, das so alt ist wie das Repository-Muster. Die kürzlich erfolgte Einführung von LINQs
IQueryable
, einer einheitlichen Darstellung einer Abfrage, hat viele Diskussionen zu diesem Thema ausgelöst.Ich bevorzuge selbst bestimmte Repositorys, nachdem ich sehr hart daran gearbeitet habe, ein generisches Repository-Framework zu erstellen. Egal welchen cleveren Mechanismus ich ausprobiert habe, ich hatte immer das gleiche Problem: Ein Repository ist Teil der zu modellierenden Domäne, und diese Domäne ist nicht generisch. Nicht jede Entität kann gelöscht werden, nicht jede Entität kann hinzugefügt werden, nicht jede Entität verfügt über ein Repository. Abfragen variieren stark; Die Repository-API wird so eindeutig wie die Entität selbst.
Ein Muster, das ich häufig verwende, besteht darin, bestimmte Repository-Schnittstellen zu haben, aber eine Basisklasse für die Implementierungen. Mit LINQ to SQL können Sie beispielsweise Folgendes tun:
Durch eine
DataContext
Arbeitseinheit Ihrer Wahl ersetzen . Eine beispielhafte Implementierung könnte sein:Beachten Sie, dass die öffentliche API des Repositorys das Löschen von Benutzern nicht zulässt. Das Aussetzen
IQueryable
ist auch eine ganz andere Dose Würmer - es gibt so viele Meinungen wie Bauchnabel zu diesem Thema.quelle
Ich bin eigentlich ein wenig anderer Meinung als Bryans Beitrag. Ich denke er hat Recht, dass letztendlich alles sehr einzigartig ist und so weiter. Gleichzeitig kommt das meiste davon beim Entwerfen heraus, und ich stelle fest, dass ich, wenn ich ein generisches Repository einrichten und es während der Entwicklung meines Modells verwenden kann, sehr schnell eine App einrichten und sie dann genauer umgestalten kann, wenn ich das finde müssen dies tun.
In solchen Fällen habe ich häufig ein generisches IRepository erstellt, das über den vollständigen CRUD-Stack verfügt und das es mir ermöglicht, schnell mit der API zu spielen und die Leute mit der Benutzeroberfläche spielen zu lassen und parallel Integrations- und Benutzerakzeptanztests durchzuführen. Wenn ich dann feststelle, dass ich bestimmte Abfragen für das Repo usw. benötige, beginne ich, diese Abhängigkeit bei Bedarf durch die spezifische zu ersetzen und gehe von dort aus. Ein zugrunde liegendes impl. ist einfach zu erstellen und zu verwenden (und möglicherweise mit einer speicherinternen Datenbank oder statischen Objekten oder verspotteten Objekten oder was auch immer zu verknüpfen).
Das heißt, was ich in letzter Zeit angefangen habe, ist das Verhalten zu brechen. Wenn Sie also Schnittstellen für IDataFetcher, IDataUpdater, IDataInserter und IDataDeleter (zum Beispiel) erstellen, können Sie Ihre Anforderungen mischen und anpassen, um Ihre Anforderungen über die Schnittstelle zu definieren, und dann Implementierungen haben, die sich um einige oder alle kümmern, und ich kann Fügen Sie immer noch die Allround-Implementierung ein, die Sie verwenden müssen, während ich die App ausbaue.
paul
quelle
GetById()
. Soll ichIRepository<T, TId>
,GetById(object id)
oder Annahmen und Verwendung machenGetById(int id)
? Wie würden zusammengesetzte Schlüssel funktionieren? Ich fragte mich, ob eine generische Auswahl nach ID eine lohnende Abstraktion war. Wenn nicht, was würden generische Repositories sonst gezwungen sein, akwardly auszudrücken? Das war die Argumentation hinter der Abstraktion der Implementierung , nicht der Schnittstelle .Ich bevorzuge bestimmte Repositorys, die vom generischen Repository (oder einer Liste generischer Repositorys zur Angabe des genauen Verhaltens) mit überschreibbaren Methodensignaturen abgeleitet sind.
quelle
Haben Sie ein generisches Repository, das von einem bestimmten Repository umschlossen wird. Auf diese Weise können Sie die öffentliche Schnittstelle steuern, haben aber dennoch den Vorteil der Wiederverwendung von Code, der aus einem generischen Repository stammt.
quelle
öffentliche Klasse UserRepository: Repository, IUserRepository
Sollten Sie IUserRepository nicht injizieren, um eine Freilegung der Schnittstelle zu vermeiden. Wie bereits erwähnt, benötigen Sie möglicherweise nicht den vollständigen CRUD-Stack usw.
quelle