Eine kleine Erklärung wird hoffentlich Ihre Verwirrung klären. Das Repository-Muster existiert, um Datenbankverbindungs- und Abfragelogik zu abstrahieren. ORMs (objektrelationale Mapper, wie EF) gibt es in der einen oder anderen Form so lange, dass viele Menschen die immense Freude und Freude am Umgang mit Spaghetti-Code, der mit SQL-Abfragen und -Abschlüssen übersät ist, vergessen oder nie gehabt haben. Zeit war, dass Sie, wenn Sie eine Datenbank abfragen wollten, tatsächlich für verrückte Dinge verantwortlich waren, wie zum Beispiel eine Verbindung herzustellen und SQL-Anweisungen tatsächlich aus Ether zu konstruieren. Der Sinn des Repository-Musters bestand darin, Ihnen einen einzigen Ort zu geben, an dem Sie all diese Gemeinheiten von Ihrem schönen, ursprünglichen Anwendungscode entfernen können.
Vorspulen auf 2014, Entity Framework und andere ORMs sind Ihr Repository.Die gesamte SQL-Logik ist sauber von Ihren neugierigen Augen entfernt und Sie haben stattdessen eine nette programmatische API, die Sie in Ihrem Code verwenden können. In einer Hinsicht ist das genug Abstraktion. Das einzige, was es nicht abdeckt, ist die Abhängigkeit vom ORM selbst. Wenn Sie später entscheiden, dass Sie Entity Framework für etwas wie NHibernate oder sogar eine Web-API deaktivieren möchten, müssen Sie eine Operation für Ihre Anwendung durchführen, um dies zu tun. Daher ist das Hinzufügen einer weiteren Abstraktionsebene immer noch eine gute Idee, aber kein Repository oder zumindest ein typisches Repository.
Das Repository, das Sie haben, ist ein typisches Repository. Es erstellt lediglich Proxies für die Entity Framework-API-Methoden. Sie rufen repo.Add
und das Repository ruft context.Add
. Es ist, ehrlich gesagt, lächerlich, und deshalb verwenden viele, einschließlich mir selbst, sagen nicht Repositories mit Entity Framework.
Also was sollte Sie tun? Erstellen Sie Dienste, oder vielleicht wird es am besten als "service-like-Klassen" bezeichnet. Wenn Services in Bezug auf .NET diskutiert werden, sprechen Sie plötzlich über alle möglichen Dinge, die für das, was wir hier diskutieren, völlig irrelevant sind. Eine serviceähnliche Klasse ist wie ein Service insofern, als sie Endpunkte aufweist, die einen bestimmten Datensatz zurückgeben oder eine bestimmte Funktion für einen bestimmten Datensatz ausführen. Während zum Beispiel mit einem typischen Repository würden Sie sich selbst Dinge zu tun, wie finden:
articleRepo.Get().Where(m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now).OrderByDescending(o => o.PublishDate)
Ihre Serviceklasse funktionieren würde wie:
service.GetPublishedArticles();
See, die gesamte Logik für das, was als eine „veröffentlichte qualifiziert Artikel "ist sauber in der Endpunktmethode enthalten. Außerdem stellen Sie mit einem Repository die zugrunde liegende API offen. Es ist einfacher mit etwas anderes zu wechseln, da der Basisdatenspeicher abstrahiert ist, aber wenn die API für die Abfrage in diesem Datenspeicher ändert, sind Sie immer noch ein Bach.
UPDATE
einrichten wäre sehr ähnlich; Der Unterschied besteht hauptsächlich darin, wie Sie einen Service gegenüber einem Repository verwenden. Nämlich, ich würde es nicht einmal abhängig machen. Mit anderen Worten, Sie haben im Wesentlichen einen Service pro Kontext, nicht pro Entität.
Wie immer mit einer Schnittstelle starten:
public interface IService
{
IEnumerable<Article> GetPublishedArticles();
...
}
Dann Implementierung ab:
public class EntityFrameworkService<TContext> : IService
where TContext : DbContext
{
protected readonly TContext context;
public EntityFrameworkService(TContext context)
{
this.context = context;
}
public IEnumerable<Article> GetPublishedArticles()
{
...
}
}
Dann Dinge beginnen, ein wenig haarig zu bekommen. In der Beispielmethode könnten Sie einfach direkt auf die DbSet
verweisen, d. H. context.Articles
, aber das impliziert Wissen über die DbSet
Namen in dem Zusammenhang. Es ist besser, context.Set<TEntity>()
für mehr Flexibilität zu verwenden. Bevor ich jedoch zu viel auf Züge zusteuere, möchte ich darauf hinweisen, warum ich diesen Namen EntityFrameworkService
genannt habe. In Ihrem Code würden Sie nur auf Ihre IService
Schnittstelle verweisen. Dann können Sie über Ihren Dependency-Injektionscontainer EntityFrameworkService<YourContext>
dafür ersetzen. Dies eröffnet die Möglichkeit, andere Dienstanbieter wie vielleicht WebApiService
, etc. zu erstellen
Jetzt möchte ich eine einzige geschützte Methode verwenden, die eine Abfrage zurückgibt, die alle meine Dienstmethoden verwenden können. Dies wird eine Menge von der cruft wie eine DbSet
Instanz jedes Mal über var dbSet = context.Set<YourEntity>();
initialisieren müssen loswerden.Das würde ein bisschen wie aussehen:
protected virtual IQueryable<TEntity> GetQueryable<TEntity>(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = null,
int? skip = null,
int? take = null)
where TEntity : class
{
includeProperties = includeProperties ?? string.Empty;
IQueryable<TEntity> query = context.Set<TEntity>();
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
query = orderBy(query);
}
if (skip.HasValue)
{
query = query.Skip(skip.Value);
}
if (take.HasValue)
{
query = query.Take(take.Value);
}
return query;
}
Beachten Sie, dass diese Methode zuerst geschützt ist. Unterklassen können es verwenden, aber dies sollte definitiv nicht Teil der öffentlichen API sein. Der springende Punkt dieser Übung besteht darin, keine abfragbaren Daten verfügbar zu machen. Zweitens ist es generisch. Mit anderen Worten, es kann mit jedem Typ umgehen, den Sie werfen, solange etwas im Kontext dafür ist.
Dann in unserem kleinen Beispiel Verfahren, würden Sie wie etwas zu tun, am Ende:
public IEnumerable<Article> GetPublishedArticles()
{
return GetQueryable<Article>(
m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now,
m => m.OrderByDescending(o => o.PublishDate)
).ToList();
}
Ein weiterer netter Trick dieses Ansatzes ist die Fähigkeit, generische Service-Methoden unter Verwendung von Schnittstellen. Sagen wir, ich wollte in der Lage sein, eine Methode zu haben, um eine veröffentlichte irgendetwas zu bekommen. Ich könnte eine Schnittstelle wie haben:
public interface IPublishable
{
PublishStatus Status { get; set; }
DateTime PublishDate { get; set; }
}
Dann alle Entitäten, die publishable sind, würde nur diese Schnittstelle implementieren. Mit dem im Ort, können Sie jetzt tun:
public IEnumerable<TEntity> GetPublished<TEntity>()
where TEntity : IPublishable
{
return GetQueryable<TEntity>(
m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now,
m => m.OrderByDescending(o => o.PublishDate)
).ToList();
}
Und dann im Anwendungscode:
service.GetPublished<Article>();
Es ist nur eine Frage, wie abstrakt man bekommen kann. EF context * ist * in der Tat ein Repository, aber ein sehr eigenwilliges Repository mit eigenen relationaldatenbankspezifischen Methoden. Wenn Sie das auch abstrahieren möchten, müssen Sie EF mit einem eigenen generischen Repository versehen. – haim770
Der Widerspruch, den ich im Internet gelesen habe, hat mich am meisten gestört. Aber jetzt haben wir das aufgeklärt, ich würde gerne wissen, wie es weiter geht. Chris Pratt sagt mit Dienstleistungen. Was sagst du? – Quoter
Dienste waren schon immer da (und wurden aufgrund des Einflusses von Domain-Driven Design populär), aber ich denke nicht, dass sie * Repositories ersetzen sollen. Soweit ich weiß, sind sie nur eine Fassade für gemeinsame Aktionen und Abfragen, und in den meisten Implementierungen sehen Sie ein "IRepository", das injiziert wird. Am Ende hängt alles vom Umfang Ihres Projekts ab. Sie müssen sich die Frage stellen, wie wichtig das gewählte Abstraktionsniveau für das Projekt ist und wie genau es sich langfristig auf dieses Projekt auswirken wird, insbesondere in Bezug auf Testbarkeit und Skalierbarkeit. – haim770