2010-09-15 11 views
7

Laut NHProf wird von der Verwendung impliziter Transaktionen abgeraten:Wie behalte ich ein IQueryable <> innerhalb einer Transaktion mithilfe des Repository-Musters?

http://nhprof.com/Learn/Alerts/DoNotUseImplicitTransactions

Allerdings gibt NHibernate LINQ beim Lesen von Objekten aus der Datenbank ein IQueryable<> zurück, und dies wird langsam ausgewertet. Ich habe diese Methode in einem Repository:

public IQueryable<T> GetAll<T>() 
{ 
    using (var transaction = _session.BeginTransaction()) 
    { 
     var data = _session.Linq<T>(); 
     transaction.Commit(); 
     return data; 
    } 
} 

Das Problem hier ist, dass die Methode die Transaktion festschreiben wird, bevor data ausgewertet wird. Gibt es eine Möglichkeit, das Repository-Muster zu verwenden und das IQueryable<> in einer expliziten Transaktion zu belassen? Oder ist es für Leseoperationen akzeptabel, implizite Transaktionen zu verwenden?

+4

Warum sollten Sie eine Transaktion um einen Lesevorgang wünschen? –

+3

Ah, aus dem Artikel: "Auch wenn wir nur Daten lesen, sollten wir eine Transaktion verwenden, weil die Verwendung von Transaktionen gewährleistet, dass wir konsistente Ergebnisse aus der Datenbank erhalten. NHibernate geht davon aus, dass der gesamte Zugriff auf die Datenbank unter einer Transaktion erfolgt rät dringend von einer Nutzung der Sitzung ohne eine Transaktion ab. " Allerdings verstehe ich immer noch nicht, warum der Autor diese Deklamation macht. –

Antwort

5

Das Repository sollte nicht eine Transaktion erstellen. Das liegt in der Verantwortung eines separaten Layers (abhängig vom Anwendungstyp).

+0

Ich stimme zu, ich denke, Ihre Erklärung, warum Sie denken, dass es die Verantwortung einer separaten Schicht ist, ist etwas wie: Transaktionsgrenzen sollten durch die Art und Weise des Benutzers oder anderer Anwendungen definiert werden, die mit der Anwendung interagieren. Beispiel für Web-Anwendungen pro Anfrage, für Dienste per Service-Aufruf, für Windows-Anwendungen per "Klick auf eine Schaltfläche", für Spiele pro Zug, etc. Wenn die Transaktionen vom Repository erstellt werden, ist es viel schwieriger, das Repository zu erstellen unabhängig von der Art und Weise, wie die Anwendung damit interagiert, und es könnte auch die Verwendung im Allgemeinen erschweren. – Paco

3

Ich würde dies umgestalten, um externe Transaktionssteuerung zu ermöglichen. Das Repository kann nicht den Umfang der Arbeitseinheit kennen, zu der verschiedene Lese-/Schreibaufrufe gehören, es sei denn, der Code, der die Aufrufe ausführt, teilt dies mit. Erwägen Sie, ein "Arbeitseinheits" -Muster einzurichten: Lassen Sie, ohne spezifische Details der Datenspeicherimplementierung preiszugeben, Objekten, die vom Repository abhängen, angeben, dass sie eine "Arbeitseinheit" beginnen, abbrechen oder abschließen.

public interface IRepository 
{ 
    public UnitOfWork BeginUnitOfWork() 

    public void CommitUOW(UnitOfWork unit) 

    public void AbortUOW(UnitOfWork unit) 

    public IQueryable<T> GetAll<T>(UnitOfWork unit) 

    public List<T> GetAll<T>() 

    public void Store<T>(T theObject, UnitOfWork unit) 

    public void Store<T>(T theObject) 
} 

Ihr Repository würde wahrscheinlich diese implementieren, indem ein eigenes Wörterbuch von SQL-Transaktionen beibehalten wird, die jeweils ein UnitOfWork Objekt verkeilt (dies so einfach wie eine leere instantiable Klasse sein kann, oder kann es Rahmen unabhängiger Informationen über Zustand liefern oder Metriken). Wenn Sie eine DB-Operation ausführen, werden Ihre Anrufer zuerst eine UOW anfordern und ihnen wird ein Token gegeben, mit dem sie den Kontext identifizieren, in dem sie einen DB-Aufruf tätigen. Das Objekt, das das Token abruft, kann es an andere Klassen übergeben, die DB-Operationen im selben Betriebskontext ausführen müssen. Die Arbeitseinheit bleibt geöffnet, bis die abhängige Klasse dem Repository mitteilt, dass sie beendet ist, was Lazy-Loads und atomare Multi-Operation-Prozeduren ermöglicht.

Beachten Sie, dass Überladungen keine Arbeitseinheiten erfordern. Es ist möglich und vielleicht notwendig, einfache Anrufe zu tätigen, ohne explizit eine Arbeitseinheit zu starten. In diesen Fällen kann Ihr Repository eine interne UOW erstellen, die angeforderte Operation ausführen und die Ergebnisse zurückgeben. Ein Lazy-Loading wird in diesen Fällen jedoch schwierig oder unmöglich sein; Sie müssen die gesamte Ergebnismenge in einer Liste abrufen, bevor Sie die interne UOW beenden.

1

Ich bin mit Diego auf diesem - Repository kann den Transaktionsbereich nicht kennen.

Ich habe auch eine Sorge über die Rückgabe von IQueryable - wie ich es verstehe, hat es eine Tonne von zusätzlichen Abfrage-Methoden, die sehr schwierig sein könnte, Einheit Test. Ich bevorzuge es, IEnumerable zurückzugeben und komplexere Abfragen in Repository-Methoden zu kapseln. Andernfalls müssen Sie alle Arten von Variationen von Abfragen mit der Ausgabe von GetAll() testen.

+1

Ist es wirklich Ihre Aufgabe, alle Methoden auf IQueryable zu testen? Sollten Sie nicht nur Unit-Test-Code sein, den Sie geschrieben haben? – Gabe

+0

@Gabe: Mein Anliegen ist nicht Unit-Testing IQueryable, es ist Unit-Tests der _results_ von Abfragen in den unzähligen Orten verwendet. Ich habe gelesen, andere sagen, es ist 6-von-1, wo die Abfrage Logik lebt, aber ich bevorzuge eine Methode wie GetFulfilledOrders() über eine komplizierte linq Abfrage, um das gleiche zurückzugeben. Einfacher zu testen und klarer. – n8wrl