2016-01-12 3 views
5

Hintergrund: Ich habe WCF-Dienst mit SimpleInjector als IoC, die Instanz von DbContext pro WCF-Anforderung erstellt.Wie überprüft man, ob DbContext eine Transaktion hat?

Backend selbst ist CQRS. CommandHandlers haben viele Dekorateure (Validierung, Autorisierung, Protokollierung, einige gemeinsame Regeln für verschiedene Behandlungsgruppen usw.) und einer von ihnen ist Transaktion Decorator:

public class TransactionCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand> 
    where TCommand : ICommand 
{ 
    private readonly ICommandHandler<TCommand> _handler; 
    private readonly IMyDbContext _context; 
    private readonly IPrincipal _principal; 

    public TransactionCommandHandlerDecorator(ICommandHandler<TCommand> handler, 
     IMyDbContext context, IPrincipal principal) 
    { 
     _handler = handler; 
     _context = context; 
     _principal = principal; 
    } 

    void ICommandHandler<TCommand>.Handle(TCommand command) 
    { 
     using (var transaction = _context.Database.BeginTransaction()) 
     { 
      try 
      { 
       var user = _context.User.Single(x => x.LoginName == _principal.Identity.Name); 
       _handler.Handle(command); 
       _context.SaveChangesWithinExplicitTransaction(user); 
       transaction.Commit(); 
      } 
      catch (Exception ex) 
      { 
       transaction.Rollback(); 
       throw; 
      } 
     } 
    } 
} 

Problem tritt auf, wenn alle Befehl versucht, Kette innerhalb der anderen Befehl ausführen gleiche WCF-Anfrage. Ich habe eine erwartete Ausnahme bei dieser Zeile:

using (var transaction = _context.Database.BeginTransaction()) 

weil meine DbContext Instanz bereits eine Transaktion besitzt.

Gibt es eine Möglichkeit, aktuelle Transaktionen zu überprüfen?

+2

Sie vermissen eine [holistische Abstraktion] (http://qujck.com/commands-and-queries-are-holistic-abstractions/) – qujck

+0

@qujck nach dem Lesen dieses Artikels Ich verstehe nicht, was der Unterschied zwischen ' IQuery 'und' IDataQuery '. Beide geben Daten zurück. Warum brauchen wir dafür eine zweite Schnittstelle? Gibt es einige Beispiele? – Szer

+0

Sie geben beide Daten zurück, aber diese verschiedenen Abstraktionen sind nur wichtig, wenn es darum geht, Ihren Code zu dekorieren. Sie benötigen eine Abstraktion, die die gesamte Operation besitzt, eine Abstraktion, die mit etwas wie einem TransactionDecorator umhüllt werden kann. Sie haben andere, untergeordnete Abstraktionen, die mit übergreifenden Themen wie "AuthoriseDecorator" oder "LoggingDecorator" dekoriert werden können, die sich nicht mit der atomaren Operation befassen. – qujck

Antwort

4

Statt die Transaktion von der DbContext von Entity Framework verwenden könnten Sie oder vielleicht sollte die TransactionScope Klasse verwenden, die einen Umgebungstransaktionsbereich erstellt und verwaltet Transaktionen aller Verbindungen zum (SQL) Datenbank unter der Decke gemacht.

Es würde sogar eine direkte SqlCommand in der gleichen Transaktion, wenn Sie die genaue (case-sensitive) Connectionstring für die SqlCommand verwenden würde. Nachrichten geschrieben an die MessageQueue sind auch in der gleichen Transaktion gekapselt

Es könnte sogar Verbindungen zu verschiedenen Datenbanken zur gleichen Zeit verwalten. Dazu verwendet es den Windows-Dienst DTC. Beachten Sie, dass dies bei der Konfiguration ein Problem darstellt. Normalerweise benötigen Sie bei einer einzelnen DB-Verbindung (oder mehreren Verbindungen zu derselben DB) keinen DTC.

Die TransactionScopeCommandHandlerDecorator Implementierung ist trivial:

public class TransactionScopeCommandHandlerDecorator<TCommand> 
     : ICommandHandler<TCommand> 
{ 
    private readonly ICommandHandler<TCommand> decoratee; 

    public TransactionScopeCommandHandlerDecorator(ICommandHandler<TCommand> decoratee) 
    { 
     this.decoratee = decoratee; 
    } 

    public void Handle(TCommand command) 
    { 
     using (var scope = new TransactionScope()) 
     { 
      this.decoratee.Handle(command); 

      scope.Complete(); 
     } 
    } 
} 

Aber: Wie qujck bereits in den Kommentaren erwähnt, werden Sie das Konzept der ICommandHandler als atomare Operation fehlt. Ein Commandhandler sollte niemals auf einen anderen Commandhandler verweisen. Das ist nicht nur schlecht für Transaktionen, sondern auch:

Stellen Sie sich vor, die Anwendung wächst und Sie würden einige Ihrer Commandhandler zu einem Hintergrund-Thread umgestalten, der in einigen Windows-Diensten ausgeführt wird. In diesem Windows-Dienst ist kein PerWcfOperation Lebensstil verfügbar. Sie würden jetzt einen LifeTimeScope Lifestyle für Ihre Commandhandler benötigen. Da Ihr Design es erlaubt, was übrigens großartig ist !, würden Sie Ihre Commandhandler normalerweise in eine LifetimeScopeCommandHandler decorator umwandeln, um die LifetimeScope zu starten. In Ihrem aktuellen Design, in dem ein einzelner Commandhandler auf andere Commandhandler verweist, werden Sie auf ein Problem stoßen, da jeder Commandhandler in seinem eigenen Bereich erstellt wird und somit ein anderer DbContext injiziert wird als die anderen Commandhandler!

Sie müssen also einige Redesign und machen Sie Ihre Commandhandler holistic abstractions und erstellen Sie eine niedrigere Abstraktion für die Durchführung der DbContext-Operationen.

+0

Dies ist der richtige Ansatz, +1. 'Es würde sogar einen direkten SqlCommand in dieselbe Transaktion setzen, wenn Sie die exakte (case-sensitive) Verbindungszeichenfolge für den SqlCommand verwenden würden. Dies ist nicht garantiert. Vorsicht, dies neigt dazu, unter Last zu versagen. – usr

+0

@ usr, bist du sicher? Ich denke du bist. Können Sie auf eine Dokumentation darüber verweisen? In einigen Szenarien verlassen wir uns sehr darauf ... Vielen Dank für diesen Hinweis! –

+0

@ Ric.Net vielen dank! Ich werde ein Redesign der Kettenbefehle in Erwägung ziehen. Aber ich sollte erwähnen, dass es nicht einfach ist. In meinem Fall, wenn der Client den Befehl AddEntityA sendet, sollte der Server der Datenbank die Entität A hinzufügen, und danach sollte er auch die Entität B hinzufügen. Der AddEntityB-Befehl hat viele Regeln und Prüfungen und einen eigenen Dekorator, wenn Entität B nicht erstellt werden kann zur Datenbank hinzugefügt. Deshalb führe ich Befehl innerhalb des Befehls – Szer

10

Ich glaube, du bist für die CurrentTransaction Eigenschaft des DbContext suchen:

var transaction = db.Database.CurrentTransaction; 

Dann können Sie einen Scheck wie folgt tun:

using(var transaction = db.Database.CurrentTransaction ?? db.Database.BeginTransaction()) 
{ 
    ... 
} 

aber ich bin nicht sicher, wie können Sie wissen, wann die Transaktion festgeschrieben werden soll, wenn sie von gleichzeitigen Methoden verwendet wird.

+0

Ich habe Ihre Antwort sehr verbessert, weil ich aus irgendeinem Grund die Version 6.0.0 von EF hatte, die diese Eigenschaft nicht hat. Nach Update-Paket geht EF zu 6.1.3 und Property ist da. Danke vielmals! – Szer

+0

@Szer Ja, es war nicht lange in EF :) –

+0

Ich realisiere, dass Ihre Antwort ist besser einfach "Antwort" auf meine Frage, aber ich muss zugeben, dass echte Antwort ist "schlechtes Design", was @ Ric.Net ist sagte. – Szer