Verwenden Sie das Repository-Muster NICHT, sondern verwenden Sie das ORM wie es ist (EF).

94

Ich habe immer das Repository-Muster verwendet, aber für mein letztes Projekt wollte ich sehen, ob ich die Verwendung und die Implementierung von „Unit Of Work“ perfektionieren kann. Je mehr ich anfing zu graben, desto mehr stellte ich mir die Frage: "Brauche ich das wirklich?"

Nun beginnt alles mit ein paar Kommentaren zu Stackoverflow mit einer Spur zu Ayende Rahiens Beitrag in seinem Blog, mit 2 spezifischen,

Dies könnte wahrscheinlich für immer und ewig besprochen werden und hängt von verschiedenen Anwendungen ab. Was ich gerne weiß,

  1. Wäre dieser Ansatz für ein Entity Framework-Projekt geeignet?
  2. Wird bei Verwendung dieses Ansatzes die Geschäftslogik immer noch in einer Serviceschicht oder in Erweiterungsmethoden ausgeführt (wie unten erläutert, weiß ich, verwendet die Erweiterungsmethode eine NHib-Sitzung)?

Dies ist mit Erweiterungsmethoden einfach möglich. Sauber, einfach und wiederverwendbar.

public static IEnumerable GetAll(
    this ISession instance, Expression<Func<T, bool>> where) where T : class
{
    return instance.QueryOver().Where(where).List();
}

Muss Ninjectich bei diesem Ansatz und als DI Contexteine Schnittstelle erstellen und diese in meine Controller einfügen?

Dejan.S
quelle

Antworten:

102

Ich bin viele Wege gegangen und habe viele Implementierungen von Repositories für verschiedene Projekte erstellt und ... Ich habe das Handtuch hineingeworfen und es aufgegeben, hier ist der Grund dafür.

Codierung für die Ausnahme

Codieren Sie die 1% ige Chance, dass sich Ihre Datenbank von einer Technologie zur anderen ändert? Wenn Sie über den zukünftigen Zustand Ihres Unternehmens nachdenken und sagen, dass dies eine Möglichkeit ist, müssen a) sie viel Geld haben, um sich eine Migration auf eine andere DB-Technologie leisten zu können, oder b) Sie wählen zum Spaß eine DB-Technologie aus oder c ) Bei der ersten Technologie, für die Sie sich entschieden haben, ist ein schrecklicher Fehler aufgetreten.

Warum die reichhaltige LINQ-Syntax wegwerfen?

LINQ und EF wurden entwickelt, damit Sie ordentliche Dinge damit tun können, um Objektgraphen zu lesen und zu durchlaufen. Das Erstellen und Verwalten eines Repositorys, das Ihnen die gleiche Flexibilität bietet, ist eine ungeheure Aufgabe. Nach meiner Erfahrung ist jedes Mal, wenn ich ein Repository erstellt habe, IMMER eine Geschäftslogik in die Repository-Schicht gelangt, um Abfragen entweder leistungsfähiger zu machen und / oder die Anzahl der Treffer in der Datenbank zu verringern.

Ich möchte nicht für jede einzelne Permutation einer Abfrage, die ich schreiben muss, eine Methode erstellen. Ich könnte genauso gut gespeicherte Prozeduren schreiben. Ich will nicht GetOrder, GetOrderWithOrderItem, GetOrderWithOrderItemWithOrderActivity, GetOrderByUserId, und so weiter ... Ich möchte nur die Haupt-Einheit und Traverse erhalten und beinhalten die Objektgraphen , wie ich so wenden Sie sich bitte.

Die meisten Beispiele für Repositories sind Bullshit

Wenn Sie nicht etwas WIRKLICH Bare-Bones wie ein Blog oder etwas entwickeln, werden Ihre Abfragen niemals so einfach sein wie 90% der Beispiele, die Sie im Internet rund um das Repository-Muster finden. Ich kann das nicht genug betonen! Dies ist etwas, das man durch den Schlamm kriechen muss, um herauszufinden. Es wird immer eine Abfrage geben, die Ihr perfekt durchdachtes Repository / Ihre perfekt durchdachte Lösung zerstört, und erst dann, wenn Sie sich selbst erraten und die technische Verschuldung / Erosion beginnt.

Teste mich nicht, Bruder

Aber was ist mit Unit-Tests, wenn ich kein Repository habe? Wie werde ich verspotten? Einfach, tust du nicht. Betrachten wir es aus beiden Blickwinkeln:

Kein Repository - Sie können das DbContextmit einem IDbContextoder anderen Tricks verspotten, aber dann testen Sie LINQ to Objects und nicht LINQ to Entities, da die Abfrage zur Laufzeit ermittelt wird ... OK, das ist also nicht gut! Nun ist es an dem Integrationstest, dies abzudecken.

Mit Repository - Sie können jetzt Ihre Repositorys verspotten und die dazwischen liegenden Layer testen. Großartig, oder? Nun, nicht wirklich ... In den oben genannten Fällen, in denen Sie Logik in die Repository-Schicht lecken müssen, um Abfragen leistungsfähiger und / oder weniger Treffer für die Datenbank zu machen, wie können Ihre Komponententests dies abdecken? Es ist jetzt in der Repo-Ebene und Sie möchten nicht IQueryable<T>richtig testen ? Seien wir auch ehrlich, Ihre Unit-Tests werden nicht die Abfragen abdecken, die eine 20-Zeilen- .Where()Klausel und enthalten.Include()'s eine Reihe von Beziehungen und schlägt die Datenbank erneut, um all diese anderen Dinge zu tun, bla, bla, bla sowieso, weil die Abfrage zur Laufzeit generiert wird. Da Sie ein Repository erstellt haben, um die Persistenz der oberen Ebenen nicht zu kennen, können Sie bei der Änderung Ihrer Datenbanktechnologie leider nicht die gleichen Ergebnisse zur Laufzeit garantieren, zurück zu Integrationstests. Der ganze Sinn des Repositorys scheint also seltsam.

2 Cent

Wir verlieren bereits viel Funktionalität und Syntax, wenn wir EF über einfach gespeicherte Prozeduren (Masseneinfügungen, Massenlöschungen, CTEs usw.) verwenden, aber ich codiere auch in C #, damit ich keine Binärdatei eingeben muss. Wir verwenden EF, damit wir die Möglichkeit haben, verschiedene Anbieter zu verwenden und unter anderem auf eine gut verwandte Art und Weise mit Objektgraphen zu arbeiten. Bestimmte Abstraktionen sind nützlich und andere nicht.

Ryan
quelle
16
Sie erstellen keine Repositorys, um sie einem Unit-Test zu unterziehen. Sie erstellen Repositorys, um die Geschäftslogik einem Unit-Test zu unterziehen . Um sicherzustellen, dass Abfragen funktionieren: Es ist viel einfacher, Integrationstests für Repositorys zu schreiben, da diese nur Logik und kein Geschäft enthalten.
Jgauffin
16
Coding for the exception: Die Verwendung von Repositorys soll nicht in der Lage sein, das Datenbankmodul zu wechseln. Es geht darum, das Geschäft von der Beharrlichkeit zu trennen.
Jgauffin
2
Dies sind alles sehr gültige Punkte mit viel Wahrheit dahinter. Was jedoch fehlt, ist die Erkenntnis, dass LINQ, das über eine Anwendung verteilt ist, anstatt auf einen konsistenten Speicherort beschränkt zu sein, das EF-Äquivalent von SQL-Aufrufen in Codebehind-Seiten erzeugt. Jede LINQ-Abfrage ist ein potenzieller Wartungspunkt in einer Anwendung. Je mehr es gibt (und je weiter verbreitet sie sind), desto höher sind die Wartungskosten und -risiken. Stellen Sie sich vor, Sie fügen einer Entität ein 'gelöschtes' Flag hinzu und müssen jeden einzelnen Ort in einer großen Anwendung, die von dieser Entität abgefragt wird, lokalisieren und jede ...
DVK
2
Ich denke, das ist kurzsichtig und abgestumpft. Warum würden Sie Logik in das Repo lecken? Und wenn ja, warum sollte das wichtig sein? Es ist eine Datenimplementierung. Alles, was wir tun, ist, den LINQ vom Rest des Codes abzuschirmen, indem wir ihn hinter dem Repo verstecken. Sie sagen, testen Sie es nicht, aber dann nutzen Sie die Möglichkeit, es nicht testen zu können, als Argument dagegen. Machen Sie also das Repo, legen Sie IQueryable nicht offen und testen Sie es nicht. Zumindest können Sie alles andere isoliert von der Datenimplementierung testen. Und diese 1% ige Chance auf einen Datenbankwechsel ist immer noch eine riesige $ -weise.
Sinaesthetic
5
+1 für diese Antwort. Ich finde, dass wir mit Entity Framework Core wirklich KEINE Repositorys benötigen. Das DbSetist das Repository und DbContextdie Arbeitseinheit . Warum ein Repository-Muster implementieren, wenn ORM dies bereits für uns erledigt? Wechseln Sie zum Testen einfach den Anbieter auf InMemory. Und mach deine Tests! Es ist in MSDN gut dokumentiert.
Mohammed Noureldin
49

Das Repository-Muster ist eine Abstraktion . Ziel ist es, die Komplexität zu reduzieren und den Rest des Codes unwissend zu machen. Als Bonus können Sie Unit- Tests anstelle von Integrationstests schreiben .

Das Problem ist, dass viele Entwickler den Zweck des Musters nicht verstehen und Repositorys erstellen, die persistenzspezifische Informationen an den Aufrufer weitergeben (normalerweise durch Offenlegen IQueryable<T>). Auf diese Weise erhalten sie keinen Vorteil gegenüber der direkten Verwendung des OP / M.

Update, um eine andere Antwort zu adressieren

Codierung für die Ausnahme

Bei der Verwendung von Repositorys geht es nicht darum, die Persistenztechnologie zu wechseln (dh die Datenbank zu ändern oder stattdessen einen Webservice usw. zu verwenden). Es geht darum, Geschäftslogik von Persistenz zu trennen, um Komplexität und Kopplung zu reduzieren.

Unit-Tests gegen Integrationstests

Sie schreiben keine Komponententests für Repositorys. Zeitraum.

Durch die Einführung von Repositorys (oder einer anderen Abstraktionsschicht zwischen Persistenz und Geschäft) können Sie jedoch Komponententests für die Geschäftslogik schreiben. Das heißt, Sie müssen sich keine Sorgen machen, dass Ihre Tests aufgrund einer falsch konfigurierten Datenbank fehlschlagen.

Wie für die Abfragen. Wenn Sie LINQ verwenden, müssen Sie auch sicherstellen, dass Ihre Abfragen funktionieren, genau wie Sie es mit Repositorys tun. und das geschieht mit Integrationstests.

Der Unterschied besteht darin, dass Sie, wenn Sie Ihr Unternehmen nicht mit LINQ-Anweisungen gemischt haben, zu 100% sicher sein können, dass Ihr Persistenzcode fehlschlägt und nicht etwas anderes.

Wenn Sie Ihre Tests analysieren, werden Sie auch feststellen, dass sie viel sauberer sind, wenn Sie keine gemischten Bedenken haben (z. B. LINQ + Geschäftslogik).

Repository-Beispiele

Die meisten Beispiele sind Bullshit. das ist sehr wahr. Wenn Sie jedoch ein Designmuster googeln, finden Sie viele beschissene Beispiele. Dies ist kein Grund, die Verwendung eines Musters zu vermeiden.

Das Erstellen einer korrekten Repository-Implementierung ist sehr einfach. In der Tat müssen Sie nur eine einzige Regel befolgen:

Fügen Sie der Repository-Klasse erst dann etwas hinzu, wenn Sie es benötigen

Viele Codierer sind faul und versuchen, ein generisches Repository zu erstellen und eine Basisklasse mit vielen Methoden zu verwenden, die sie möglicherweise benötigen. YAGNI. Sie schreiben die Repository-Klasse einmal und behalten sie so lange, wie die Anwendung lebt (kann Jahre sein). Warum es versauen, indem man faul ist? Halten Sie es sauber, ohne dass eine Basisklasse vererbt wird. Dies erleichtert das Lesen und Verwalten erheblich.

(Die obige Aussage ist eine Richtlinie und kein Gesetz. Eine Basisklasse kann sehr gut motiviert sein. Denken Sie einfach nach, bevor Sie sie hinzufügen, damit Sie sie aus den richtigen Gründen hinzufügen.)

Altes Zeug

Fazit:

Wenn es Ihnen nichts ausmacht, LINQ-Anweisungen in Ihrem Geschäftscode zu haben oder sich um Komponententests zu kümmern, sehe ich keinen Grund, Entity Framework nicht direkt zu verwenden.

Aktualisieren

Ich habe sowohl über das Repository-Muster als auch darüber gebloggt, was "Abstraktion" wirklich bedeutet: http://blog.gauffin.org/2013/01/repository-pattern-done-right/

Update 2

Wie werden Sie für einen einzelnen Entitätstyp mit mehr als 20 Feldern eine Abfragemethode entwerfen, um eine Permutationskombination zu unterstützen? Sie möchten die Suche nicht nur nach Namen einschränken. Wie wäre es mit der Suche mit Navigationseigenschaften? Listen Sie alle Bestellungen mit Artikeln mit einem bestimmten Preiscode auf. 3 Ebenen der Suche nach Navigationseigenschaften. Der ganze Grund, IQueryableder erfunden wurde, war, in der Lage zu sein, eine beliebige Kombination von Suche gegen Datenbank zusammenzustellen. Theoretisch sieht alles gut aus, aber das Bedürfnis des Benutzers gewinnt über der Theorie.

Nochmals: Eine Entität mit mehr als 20 Feldern ist falsch modelliert. Es ist eine GOTTES Einheit. Mach es kaputt.

Ich behaupte nicht, dass IQueryabledas nicht zum Abfragen gemacht war. Ich sage, dass es für eine Abstraktionsschicht wie das Repository-Muster nicht richtig ist, da es undicht ist. Es gibt keinen 100% vollständigen LINQ To Sql-Anbieter (wie EF).

Sie alle haben implementierungsspezifische Dinge wie das eifrige Laden oder das Ausführen von SQL "IN" -Anweisungen. Das Offenlegen IQueryableim Repository zwingt den Benutzer, all diese Dinge zu wissen. Somit ist der gesamte Versuch, die Datenquelle zu abstrahieren, ein völliger Fehlschlag. Sie erhöhen lediglich die Komplexität, ohne einen Vorteil gegenüber der direkten Verwendung des OP / M zu erzielen.

Implementieren Sie das Repository-Muster entweder korrekt oder verwenden Sie es überhaupt nicht.

(Wenn Sie wirklich mit großen Entitäten umgehen möchten, können Sie das Repository-Muster mit dem Spezifikationsmuster kombinieren . Dadurch erhalten Sie eine vollständige Abstraktion, die auch testbar ist.)

jgauffin
quelle
6
Wenn IQueryable nicht verfügbar gemacht wird, führt dies zu einer eingeschränkten Suche, und die Benutzer erstellen am Ende mehr Get-Methoden für verschiedene Arten von Abfragen. Dadurch wird das Repository schließlich komplexer.
Akash Kava
3
Sie haben das Kernproblem überhaupt nicht angesprochen: Die Bereitstellung von IQueryable über ein Repository ist keine vollständige Abstraktion.
Jgauffin
1
Ein Abfrageobjekt zu haben, das die gesamte erforderliche Infrastruktur enthält, die an und für sich ausgeführt werden muss, ist der richtige Weg. Sie geben ihm die Felder, die die Suchbegriffe sind, und es gibt eine Liste der Ergebnisse zurück. Innerhalb der QO können Sie tun, was Sie wollen. Und es ist eine Schnittstelle, die so einfach zu testen ist. Siehe meinen Beitrag oben. Es ist das Beste.
h.alex
2
Persönlich halte ich es auch für sinnvoll, die IQueryable <T> -Schnittstelle in einer Repository-Klasse zu implementieren, anstatt die zugrunde liegende Menge in einem ihrer Mitglieder verfügbar zu machen.
dark_perfect
3
@yat: Ein Repos pro Aggregatwurzel. Aber imho ist es nicht Aggregat Root und Aggregat von Tabellen, sondern nur Aggregat Root und Aggregate . Der tatsächliche Speicher verwendet möglicherweise nur eine Tabelle oder viele davon, dh es handelt sich möglicherweise nicht um eine Eins-Eins-Zuordnung zwischen jedem Aggregat und einer Tabelle. Ich verwende Repositorys, um die Komplexität zu reduzieren und Abhängigkeiten des zugrunde liegenden Speichers zu entfernen.
Jgauffin
26

IMO haben sowohl die RepositoryAbstraktion als auch die UnitOfWorkAbstraktion einen sehr wertvollen Platz in jeder sinnvollen Entwicklung. Die Leute werden über Implementierungsdetails streiten, aber genauso wie es viele Möglichkeiten gibt, eine Katze zu häuten, gibt es viele Möglichkeiten, eine Abstraktion zu implementieren.

Ihre Frage ist speziell zu verwenden oder nicht zu verwenden und warum.

Wie Sie zweifellos festgestellt haben, haben Sie beide Muster bereits in Entity Framework integriert, DbContextist das UnitOfWorkund DbSetist das Repository. Sie müssen das UnitOfWorkoder sich Repositoryselbst im Allgemeinen nicht einem Unit-Test unterziehen , da dies lediglich die Erleichterung zwischen Ihren Klassen und den zugrunde liegenden Datenzugriffsimplementierungen erleichtert. Was Sie immer wieder tun müssen, ist, diese beiden Abstraktionen zu verspotten, wenn Sie die Logik Ihrer Dienste testen.

Sie können mit externen Bibliotheken verspotten, fälschen oder was auch immer, indem Sie Ebenen von Codeabhängigkeiten (die Sie nicht steuern) zwischen der Logik, die den Test durchführt, und der zu testenden Logik hinzufügen .

Ein kleiner Punkt ist also, dass Sie eine eigene Abstraktion haben UnitOfWorkund Repositorymaximale Kontrolle und Flexibilität erhalten, wenn Sie Ihre Unit-Tests verspotten.

Alles sehr gut, aber für mich ist die wahre Kraft dieser Abstraktionen, dass sie eine einfache Möglichkeit bieten, aspektorientierte Programmiertechniken anzuwenden und die SOLID-Prinzipien einzuhalten .

So haben Sie Ihre IRepository:

public interface IRepository<T>
    where T : class
{
    T Add(T entity);
    void Delete(T entity);
    IQueryable<T> AsQueryable();
}

Und seine Umsetzung:

public class Repository<T> : IRepository<T>
    where T : class
{
    private readonly IDbSet<T> _dbSet;
    public Repository(PPContext context) 
    {
        _dbSet = context.Set<T>();
    }

    public T Add(T entity)
    { 
        return _dbSet.Add(entity); 
    }

    public void Delete(T entity)
    {
        _dbSet.Remove(entity); 
    }

    public IQueryable<T> AsQueryable() 
    {
        return _dbSet.AsQueryable();
    }
}

Nichts Außergewöhnliches so weit , aber jetzt wollen wir einige Protokollierung hinzufügen - einfach , mit einem Logging - Ausstatter .

public class RepositoryLoggerDecorator<T> : IRepository<T>
    where T : class
{
    Logger logger = LogManager.GetCurrentClassLogger();
    private readonly IRepository<T> _decorated;
    public RepositoryLoggerDecorator(IRepository<T> decorated)
    {
        _decorated = decorated;
    }

    public T Add(T entity)
    {
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString() );
        T added = _decorated.Add(entity);
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
        return added;
    }

    public void Delete(T entity)
    {
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
        _decorated.Delete(entity);
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
    }

    public IQueryable<T> AsQueryable()
    {
        return _decorated.AsQueryable();
    }
}

Alles erledigt und ohne Änderung an unserem bestehenden Code . Es gibt zahlreiche andere Querschnittsthemen, die wir hinzufügen können, wie z. B. Ausnahmebehandlung, Daten-Caching, Datenvalidierung oder was auch immer, und während unseres gesamten Entwurfs- und Erstellungsprozesses das Wertvollste, was wir haben, um einfache Funktionen hinzuzufügen, ohne unseren vorhandenen Code zu ändern ist unsere IRepositoryAbstraktion .

Jetzt habe ich diese Frage in StackOverflow oft gesehen: "Wie lässt sich Entity Framework in einer Umgebung mit mehreren Mandanten arbeiten?".

https://stackoverflow.com/search?q=%5Bentity-framework%5D+multi+tenant

Wenn Sie eine RepositoryAbstraktion haben, lautet die Antwort: "Es ist einfach, einen Dekorateur hinzuzufügen."

public class RepositoryTennantFilterDecorator<T> : IRepository<T>
    where T : class
{
    //public for Unit Test example
    public readonly IRepository<T> _decorated;
    public RepositoryTennantFilterDecorator(IRepository<T> decorated)
    {
        _decorated = decorated;
    }

    public T Add(T entity)
    {
        return _decorated.Add(entity);
    }

    public void Delete(T entity)
    {
        _decorated.Delete(entity);
    }

    public IQueryable<T> AsQueryable()
    {
        return _decorated.AsQueryable().Where(o => true);
    }
}

IMO sollten Sie immer eine einfache Abstraktion über Komponenten von Drittanbietern platzieren, auf die an mehr als einer Handvoll Stellen verwiesen wird. Aus dieser Perspektive ist ein ORM der perfekte Kandidat, da in so vielen Teilen unseres Codes darauf verwiesen wird.

Die Antwort, die normalerweise in den Sinn kommt, wenn jemand sagt: "Warum sollte ich eine Abstraktion (z. B. Repository) über diese oder jene Bibliothek von Drittanbietern haben?" Lautet: "Warum sollten Sie nicht?"

PS-Dekorateure lassen sich sehr einfach mit einem IoC-Container wie SimpleInjector anwenden .

[TestFixture]
public class IRepositoryTesting
{
    [Test]
    public void IRepository_ContainerRegisteredWithTwoDecorators_ReturnsDecoratedRepository()
    {
        Container container = new Container();
        container.RegisterLifetimeScope<PPContext>();
        container.RegisterOpenGeneric(
            typeof(IRepository<>), 
            typeof(Repository<>));
        container.RegisterDecorator(
            typeof(IRepository<>), 
            typeof(RepositoryLoggerDecorator<>));
        container.RegisterDecorator(
            typeof(IRepository<>), 
            typeof(RepositoryTennantFilterDecorator<>));
        container.Verify();

        using (container.BeginLifetimeScope())
        {
            var result = container.GetInstance<IRepository<Image>>();

            Assert.That(
                result, 
                Is.InstanceOf(typeof(RepositoryTennantFilterDecorator<Image>)));
            Assert.That(
                (result as RepositoryTennantFilterDecorator<Image>)._decorated,
                Is.InstanceOf(typeof(RepositoryLoggerDecorator<Image>)));
        }
    }
}
qujck
quelle
11

Erstens ist EF selbst, wie aus einer Antwort hervorgeht, ein Repository-Muster. Es ist nicht erforderlich, eine weitere Abstraktion zu erstellen, nur um es als Repository zu bezeichnen.

Mockable Repository für Unit Tests, brauchen wir es wirklich?

Wir lassen EF in Unit-Tests kommunizieren, um die DB zu testen, um unsere Geschäftslogik direkt mit der SQL-Test-DB zu testen. Ich sehe keinen Vorteil darin, ein Repository-Muster überhaupt zu verspotten. Was ist wirklich falsch an Unit-Tests gegen Testdatenbanken? Da es sich um Massenoperationen handelt, sind diese nicht möglich und wir schreiben am Ende Roh-SQL. SQLite im Speicher ist der perfekte Kandidat für Unit-Tests mit realen Datenbanken.

Unnötige Abstraktion

Möchten Sie ein Repository erstellen, damit Sie EF in Zukunft problemlos durch NHbibernate usw. oder etwas anderes ersetzen können? Klingt nach einem guten Plan, ist er aber wirklich kostengünstig?

Linq tötet Unit-Tests?

Ich würde gerne Beispiele sehen, wie es töten kann.

Abhängigkeitsinjektion, IoC

Wow, das sind großartige Worte, sicher, dass sie theoretisch großartig aussehen, aber manchmal muss man sich zwischen großartigem Design und großartiger Lösung entscheiden. Wir haben das alles genutzt und am Ende alles in den Müll geworfen und einen anderen Ansatz gewählt. Größe gegen Geschwindigkeit (Größe des Codes und Geschwindigkeit der Entwicklung) sind im wirklichen Leben von großer Bedeutung. Benutzer benötigen Flexibilität, es ist ihnen egal, ob Ihr Code in Bezug auf DI oder IoC großartig im Design ist.

Es sei denn, Sie erstellen Visual Studio

All diese großartigen Designs werden benötigt, wenn Sie ein komplexes Programm wie Visual Studio oder Eclipse erstellen, das von vielen Menschen entwickelt wird und in hohem Maße anpassbar sein muss. Alle großartigen Entwicklungsmuster kamen nach Jahren der Entwicklung dieser IDEs ins Spiel, und sie haben sich an einem Ort entwickelt, an dem all diese großartigen Designmuster so wichtig sind. Wenn Sie jedoch eine einfache webbasierte Gehaltsabrechnung oder eine einfache Geschäftsanwendung durchführen, ist es besser, dass Sie Ihre Entwicklung im Laufe der Zeit weiterentwickeln, anstatt Zeit damit zu verbringen, sie für Millionen Benutzer zu erstellen, wo sie nur für Hunderte von Benutzern bereitgestellt wird.

Repository als gefilterte Ansicht - ISecureRepository

Auf der anderen Seite sollte das Repository eine gefilterte Ansicht von EF sein, die den Zugriff auf Daten schützt, indem der erforderliche Füllstoff basierend auf dem aktuellen Benutzer / der aktuellen Rolle angewendet wird.

Dies erschwert das Repository jedoch noch mehr, da es in einer riesigen Codebasis zu warten ist. Am Ende erstellen die Benutzer unterschiedliche Repositorys für unterschiedliche Benutzertypen oder eine Kombination von Entitätstypen. Nicht nur das, wir haben auch viele DTOs.

Die folgende Antwort ist eine Beispielimplementierung von Filtered Repository, ohne eine ganze Reihe von Klassen und Methoden zu erstellen. Es kann sein, dass die Frage nicht direkt beantwortet wird, aber es kann nützlich sein, eine abzuleiten.

Haftungsausschluss: Ich bin Autor des Entity REST SDK.

http://entityrestsdk.codeplex.com

Vor diesem Hintergrund haben wir ein SDK entwickelt, das auf der Grundlage von SecurityContext ein Repository für gefilterte Ansichten erstellt, das Filter für CRUD-Operationen enthält. Und nur zwei Arten von Regeln vereinfachen komplexe Operationen. Der erste ist der Zugriff auf die Entität und der andere ist die Lese- / Schreibregel für die Eigenschaft.

Der Vorteil ist, dass Sie keine Geschäftslogik oder Repositorys für verschiedene Benutzertypen neu schreiben, sondern sie einfach blockieren oder ihnen den Zugriff gewähren.

public class DefaultSecurityContext : BaseSecurityContext {

  public static DefaultSecurityContext Instance = new DefaultSecurityContext();

  // UserID for currently logged in User
  public static long UserID{
       get{
             return long.Parse( HttpContext.Current.User.Identity.Name );
       }
  }

  public DefaultSecurityContext(){
  }

  protected override void OnCreate(){

        // User can access his own Account only
        var acc = CreateRules<Account>();

        acc.SetRead( y => x=> x.AccountID == UserID ) ;
        acc.SetWrite( y => x=> x.AccountID == UserID );

        // User can only modify AccountName and EmailAddress fields
        acc.SetProperties( SecurityRules.ReadWrite, 
              x => x.AccountName,
              x => x.EmailAddress);

        // User can read AccountType field
        acc.SetProperties<Account>( SecurityRules.Read, 
              x => x.AccountType);

        // User can access his own Orders only
        var order = CreateRules<Order>();
        order.SetRead( y => x => x.CustomerID == UserID );

        // User can modify Order only if OrderStatus is not complete
        order.SetWrite( y => x => x.CustomerID == UserID 
            && x.OrderStatus != "Complete" );

        // User can only modify OrderNotes and OrderStatus
        order.SetProperties( SecurityRules.ReadWrite, 
              x => x.OrderNotes,
              x => x.OrderStatus );

        // User can not delete orders
        order.SetDelete(order.NotSupportedRule);
  }
}

Diese LINQ-Regeln werden für jede Operation anhand der Datenbank in der SaveChanges-Methode ausgewertet, und diese Regeln fungieren als Firewall vor der Datenbank.

Akash Kava
quelle
3
Unit-Tests gegen eine DB bedeuten, dass Sie zusätzliche externe Anforderungen für Ihre Tests haben. Wenn diese Datenbank nicht verfügbar ist oder die Daten gelöscht werden oder etwas mit dieser Datenbank passiert, schlagen Ihre Tests fehl. Dies ist nicht erwünscht. Die Einrichtung von Repositorys, die IQueryable verfügbar machen, dauert ca. 2 Minuten. Hier wird keine Zeit verschwendet. Warum hast du lange gebraucht? All dies dauert Minuten. Ich werde sagen, dass dies alles hervorragend für Unit-Tests meiner komplexen Abfragen in meiner Service-Schicht funktioniert hat. Es war so schön, keine Datenbank zu benötigen, um eine Verbindung herzustellen. Das Erhalten eines spöttischen Frameworks von Nuget dauerte ungefähr eine Minute. Dieses Zeug braucht keine Zeit.
user441521
@ user441521 Repositorys mit IQueryable 2 Minuten zum Einrichten? In welcher Welt Sie leben, jede asp.net-Anfrage auf unserer Live-Site wird innerhalb von Millisekunden bearbeitet. Das Verspotten und Fälschen usw. erhöht die Komplexität des Codes und verschwendet Zeit. Unit-Tests sind nutzlos, wenn Unit nicht als Business Logic Unit definiert ist.
Akash Kava
7

Es gibt viele Debatten darüber, welche Methode korrekt ist, daher betrachte ich sie als akzeptabel, sodass ich immer die verwende, die mir am besten gefällt (Welches ist kein Repository, UoW).

In EF wird UoW über DbContext implementiert und die DbSets sind Repositorys.

Was die Arbeit mit der Datenschicht betrifft, arbeite ich direkt am DbContext-Objekt. Bei komplexen Abfragen werde ich Erweiterungsmethoden für die Abfrage erstellen, die wiederverwendet werden können.

Ich glaube, Ayende hat auch einige Posts darüber, wie schlecht es ist, CUD-Operationen zu abstrahieren.

Ich mache immer eine Schnittstelle und lasse meinen Kontext davon erben, damit ich einen IoC-Container für DI verwenden kann.

Josh
quelle
Wie umfangreich sind die Erweiterungsmethoden? Nehmen wir an, ich muss den Status einer anderen Entität in meiner Erweiterung abrufen. Das ist momentan meine größte Sorge. Haben Sie etwas dagegen, einige Beispiele für Erweiterungsmethoden zu zeigen?
Dejan.S
ayende.com/blog/153473/… und ayende.com/blog/153569/… . (Dies sind Übersichten über eine Architektur (Framework?) Namens S # arp lite. Meistens gut, aber er ist mit den Repositories und CUD-Abstraktionen nicht einverstanden.)
Josh
Es basiert auf NHibernate. Haben Sie keine Beispiele für EF? Und wieder, wenn ich eine andere Entität aufrufen muss, wie wird das bei der statischen Erweiterungsmethode am besten gemacht?
Dejan.S
3
Dies ist alles in Ordnung, bis eine Eigenschaft Ihres Domänenobjekts durch Daten hydratisiert werden muss, die nicht in Ihrer Datenbank gespeichert sind. oder Sie müssen auf eine Technologie zurückgreifen, die leistungsfähiger ist als Ihr aufgeblähtes ORM. HOPPLA! Ein ORM ist einfach KEIN Ersatz für ein Repository, sondern ein Implementierungsdetail von einem.
CDAQ
2

Was für EF am häufigsten gilt, ist kein Repository-Muster. Es ist ein Fassadenmuster (Abstraktion der Aufrufe von EF-Methoden in einfachere, benutzerfreundlichere Versionen).

EF ist derjenige, der das Repository-Muster (und auch das Muster der Arbeitseinheit) anwendet. Das heißt, EF ist derjenige, der die Datenzugriffsschicht abstrahiert, sodass der Benutzer keine Ahnung hat, dass es sich um SQLServer handelt.

Und dabei sind die meisten "Repositories" über EF nicht einmal gute Fassaden, da sie lediglich einzelne Methoden in EF auf einfache Weise abbilden, selbst wenn sie dieselben Signaturen haben.

Die beiden Gründe für die Anwendung dieses sogenannten "Repository" -Musters auf EF bestehen darin, das Testen zu vereinfachen und eine Teilmenge von "vordefinierten" Aufrufen darauf zu erstellen. An sich nicht schlecht, aber eindeutig kein Repository.

Aratirn
quelle
1

Linq ist heutzutage ein "Repository".

ISession + Linq ist bereits das Repository, und Sie benötigen weder GetXByYMethoden noch QueryData(Query q)Verallgemeinerungen. Da ich ein wenig paranoid gegenüber der DAL-Nutzung bin, bevorzuge ich immer noch die Repository-Schnittstelle. (Unter dem Gesichtspunkt der Wartbarkeit müssen wir auch noch eine Fassade über bestimmte Datenzugriffsschnittstellen haben).

Hier ist das Repository, das wir verwenden - es entkoppelt uns von der direkten Verwendung von nhibernate, bietet jedoch eine Linq-Schnittstelle (als ISession-Zugriff in Ausnahmefällen, die möglicherweise einem Refactor unterliegen).

class Repo
{
    ISession _session; //via ioc
    IQueryable<T> Query()
    {
        return _session.Query<T>();
    }
}
mikalai
quelle
Was tun Sie für die Serviceschicht?
Dejan.S
Controller fragen Repo nach schreibgeschützten Daten ab. Warum eine zusätzliche Ebene hinzufügen? Die andere Möglichkeit ist die Verwendung von "ContentService", bei dem es sich immer mehr um ein Service-Level-Repository handelt: GetXByY usw. usw. Für Änderungsvorgänge - Anwendungsdienste sind nur Abstraktionen über Anwendungsfälle - verwenden sie BL und repo frei ..
mikalai
Ich bin es gewohnt, Service Layer für Geschäftslogik zu erstellen. Ich bin mir nicht sicher, ob ich Ihnen mit ContentService folge. Bitte erläutern Sie dies. Wäre es eine schlechte Praxis, Hilfsklassen als "Serviceschicht" durchzuführen?
Dejan.S
Mit "Service-Schicht" meinte ich "Anwendungsdienste". Sie können das Repository und jeden anderen öffentlichen Teil der Domänenschicht verwenden. "Service Layer" ist keine schlechte Praxis, aber ich würde es vermeiden, eine XService-Klasse zu erstellen, nur um das List <X> -Ergebnis bereitzustellen. Das Kommentarfeld scheint zu kurz zu sein, um die Dienste im Detail zu beschreiben.
Mikalai
Was ist, wenn beispielsweise eine Warenkorbberechnung erforderlich ist und Sie Anwendungseinstellungsparameter und bestimmte Kundenparameter abrufen müssen, um eine Berechnung durchzuführen. Diese wird an mehreren Stellen in der Anwendung wiederverwendet. Wie gehen Sie mit dieser Situation um? Hilfsklasse oder Anwendungsservice?
Dejan.S
1

Im Repository (oder wie auch immer man es nennt) geht es mir derzeit hauptsächlich darum, die Persistenzschicht zu abstrahieren.

Ich verwende es gekoppelt mit Abfrageobjekten, damit ich in meinen Anwendungen keine Kopplung an eine bestimmte Technologie habe. Und es erleichtert auch das Testen erheblich.

Also neige ich dazu zu haben

public interface IRepository : IDisposable
{
    void Save<TEntity>(TEntity entity);
    void SaveList<TEntity>(IEnumerable<TEntity> entities);

    void Delete<TEntity>(TEntity entity);
    void DeleteList<TEntity>(IEnumerable<TEntity> entities);

    IList<TEntity> GetAll<TEntity>() where TEntity : class;
    int GetCount<TEntity>() where TEntity : class;

    void StartConversation();
    void EndConversation();

    //if query objects can be self sustaining (i.e. not need additional configuration - think session), there is no need to include this method in the repository.
    TResult ExecuteQuery<TResult>(IQueryObject<TResult> query);
}

Fügen Sie möglicherweise asynchrone Methoden mit Rückrufen als Delegaten hinzu. Das Repo ist einfach generisch zu implementieren , sodass ich keine Zeile der Implementierung von App zu App berühren kann. Nun, das ist zumindest wahr, wenn ich NH benutze. Ich habe es auch mit EF gemacht, aber ich habe EF gehasst. 4. Das Gespräch ist der Beginn einer Transaktion. Sehr cool, wenn einige Klassen die Repository-Instanz gemeinsam nutzen. Für NH entspricht ein Repo in meiner Implementierung einer Sitzung, die bei der ersten Anforderung geöffnet wird.

Dann die Abfrageobjekte

public interface IQueryObject<TResult>
{
    /// <summary>Provides configuration options.</summary>
    /// <remarks>
    /// If the query object is used through a repository this method might or might not be called depending on the particular implementation of a repository.
    /// If not used through a repository, it can be useful as a configuration option.
    /// </remarks>
    void Configure(object parameter);

    /// <summary>Implementation of the query.</summary>
    TResult GetResult();
}

Für die Konfiguration verwende ich in NH nur, um die ISession zu übergeben. In EF macht mehr oder weniger keinen Sinn.

Eine Beispielabfrage wäre .. (NH)

public class GetAll<TEntity> : AbstractQueryObject<IList<TEntity>>
    where TEntity : class
{
    public override IList<TEntity> GetResult()
    {
        return this.Session.CreateCriteria<TEntity>().List<TEntity>();
    }
}

Um eine EF-Abfrage durchzuführen, müsste sich der Kontext in der Abstract-Basis befinden, nicht in der Sitzung. Aber natürlich wäre das ifc dasselbe.

Auf diese Weise sind die Abfragen selbst gekapselt und leicht testbar. Das Beste ist, dass mein Code nur auf Schnittstellen basiert. Alles ist sehr sauber. Domänen- (Geschäfts-) Objekte sind genau das, z. B. gibt es keine Vermischung von Verantwortlichkeiten wie bei Verwendung des aktiven Datensatzmusters, das kaum testbar ist und Datenzugriffscode (Abfragecode) im Domänenobjekt mischt, und dabei Bedenken zu mischen (Objekt, das abruft) selbst??). Es steht weiterhin jedem frei, POCOs für die Datenübertragung zu erstellen.

Alles in allem bietet dieser Ansatz viel Wiederverwendung und Einfachheit von Code, ohne dass etwas verloren geht, was ich mir vorstellen kann. Irgendwelche Ideen?

Und vielen Dank an Ayende für seine großartigen Beiträge und sein anhaltendes Engagement. Es sind seine Ideen hier (Abfrageobjekt), nicht meine.

h.alex
quelle
1
Persistenzentitäten (Ihre POCOs) sind KEINE Geschäfts- / Domänenentitäten. Der Zweck des Repositorys besteht darin, die Geschäftsschicht (oder eine beliebige Schicht) von der Persistenz zu entkoppeln.
MikeSW
Ich sehe die Kupplung nicht. Ich stimme dem POCO-Teil etwas zu, aber es ist mir egal. Nichts hindert Sie daran, "echte" POCOs zu haben und diesen Ansatz dennoch zu verwenden.
h.alex
1
Entitäten müssen überhaupt keine dummen POCOs sein. Tatsächlich ist die Modellierung der Geschäftslogik in Entitäten das, was die DDD-Crowd ständig tut. Dieser Entwicklungsstil passt sehr gut zu NH oder EF.
Chris
1

Für mich ist es eine einfache Entscheidung mit relativ wenigen Faktoren. Die Faktoren sind:

  1. Repositorys sind für Domänenklassen.
  2. In einigen meiner Apps sind Domänenklassen dieselben wie meine Persistenzklassen (DAL), in anderen nicht.
  3. Wenn sie gleich sind, stellt EF mir bereits Repositories zur Verfügung.
  4. EF bietet Lazy Loading und IQueryable. Ich mag diese.
  5. Das Abstrahieren / 'Facading' / Neuimplementieren des Repositorys über EF bedeutet normalerweise den Verlust von Faulheit und IQueryable

Wenn meine App also nicht die Nummer 2 rechtfertigen kann, trennen Sie Domain- und Datenmodelle, dann kümmere ich mich normalerweise nicht um Nummer 5.

Kreidig
quelle