2016-04-07 9 views
1

Eigentlich fand ich versuche, die beste Art und Weise Transaktion mit Nhibernate mit Repository-Muster in MVC 5 KontextWas ist der beste Weg Transaktion mit Nhibernate Repository verwalten

Sie meine Probe gefunden Projekt hier kann zu verwalten: https://github.com/Nono31/Pixel.Sample Mein Repository genannt von Manager Und war mein Manager von Controller

genannt

Eigentlich funktioniert alles in Ordnung, aber wenn ich NHProfiler beginnen, ich habe eine Warnung „Verwendung von impliziten Transaktionen abgeraten“ (http://www.hibernatingrhinos.com/products/nhprof/learn/alert/donotuseimplicittransactions)

Meine Frage ist, wie implizite Transaktion in meinem Kontext vermieden werden kann? In welcher Schicht verwalten Sie die Transaktion? Wenn ich meine Transaktion auf der Repository-Schicht verwalte, wird die Lazy Loading-Entität außerhalb der Transaktion aufgerufen. Ich habe eine Lösung mit ActionFilterAttribute gesehen, aber es gibt noch eine andere Lösung?

public class UnitOfWorkAction : ActionFilterAttribute 
{ 
    public override void OnActionExecuting(ActionExecutingContext context) 
    { 
     base.OnActionExecuting(context); 

     if (!context.IsChildAction) 
     { 
      var session = DependencyResolver.Current.GetService<ISession>(); 
      session.BeginTransaction(); 
     } 
    } 

    public override void OnResultExecuted(ResultExecutedContext context) 
    { 
     base.OnResultExecuted(context); 

     if (context.IsChildAction) return; 

     using (var session = DependencyResolver.Current.GetService<ISession>()) 
     { 
      if (session.Transaction != null && session.Transaction.IsActive) 
       using (var transaction = session.Transaction) 
       { 
        try 
        { 
         var thereWereNoExceptions = context.Exception == null || context.ExceptionHandled; 
         if (context.Controller.ViewData.ModelState.IsValid && thereWereNoExceptions) 
          transaction.Commit(); 
         else 
          transaction.Rollback(); 
        } 
        catch 
        { 
         transaction.Rollback(); 
         throw; 
        } 
        finally 
        { 
         session.Close(); 
        } 
       } 
     } 
    } 
} 
+0

Welche Methode gibt genau dieses NHProf-Problem? Ihr Repository sieht gut aus und die Verwendung einer Delegat-Wrapper-Lösung scheint einwandfrei zu funktionieren. –

+0

Entschuldigung, alle meine Select-Methode wird in Transaktion in FooController Beispiel aufgerufen, aber zum Beispiel, wenn Sie Foo.Bar wo Bar Verweis auf foo Objekt in meinem Controller ist, wird die Bar-Auswahl aus Transaktion – Arnaud

Antwort

1

Sie sollten Ihre gesamte Arbeitseinheit abwickeln. Arbeitseinheit sollte die Arbeit abdecken, die Sie tun, um Ihre Ansichtsmodelle zu holen.

Dadurch wird faul-Laden vermeiden aus einer Transaktion erfolgen.

Es erlaubt Ihnen auch die komplette Arbeitseinheit im Fehlerfall rückgängig zu machen.
Und es hat einen Leistungsvorteil: Einer der Gründe für NHProfiler Warnung ist die Kosten für das Öffnen einer Transaktion für jeden Datenzugriff. (Es gibt andere, z. B. den Cache der zweiten Ebene, der eine explizite Transaktion erfordert, andernfalls wird er bei Aktualisierungen deaktiviert.)

Sie können die verwenden, die Sie gefunden haben.
Ich persönlich finde es auch "breit". Es enthält in der Transaktion sogar die Ergebnisausführung. Dies ermöglicht die Verwendung von Lazy-Loading in Ansichten. Ich denke, wir sollten keine Entitäten als Ansichtsmodelle verwenden, und das Auslösen des DB-Zugriffs aus Ansichten ist für mich noch schlimmer. Die eine, die ich benutze, beendet die Transaktion in OnActionExecuted.
Darüber hinaus sieht seine Fehlerbehandlung für mich etwas spezifisch aus. Rollback bei ungültigem Modellstatus führt möglicherweise nicht zu sens: keine Aktion soll versuchen, ungültige Daten in der DB zu speichern. Das Rückgängigmachen der Rollback-Funktion für behandelte Ausnahmen ist weniger seltsam: Wenn die MVC-Pipeline eine Ausnahme festgestellt hat, bedeutet dies, dass bei der Aktion oder beim Ausführen des Ergebnisses ein Fehler aufgetreten ist. Einige andere Filter haben jedoch die Ausnahme behandelt. Normalerweise ist nur ein Fehlerfilter, der eine Fehlerseite anzeigt, nicht wahr? Dann verursacht eine solche Logik eine fehlgeschlagene Aktion ...

Hier wird das Muster des einen ist die ich benutze:

public class DefaultTransactionAttribute : ActionFilterAttribute 
{ 
    private static readonly ILog Logger = 
     LogManager.GetLogger(typeof(DefaultTransactionAttribute)); 

    public override void OnActionExecuting(ActionExecutingContext filterContext) 
    { 
     // IUnitOfWork is some kind of custom ISession encapsulation. 
     // I am working in a context in which we may change the ORM, so 
     // I am hiding it. 
     var uow = DependencyResolver.Current.GetService<IUnitOfWork>(); 
     uow.BeginTransaction(); 
     base.OnActionExecuting(filterContext); 
    } 

    public override void OnActionExecuted(ActionExecutedContext filterContext) 
    { 
     base.OnActionExecuted(filterContext); 
     var uow = DependencyResolver.Current.GetService<IUnitOfWork>(); 
     if (!uow.HasActiveTransaction()) 
     { 
      // Log rather than raise an exception, for avoiding hiding 
      // another failure. 
      Logger.Warn("End of action without a running transaction. " + 
       "Check how this can occur and try avoid this."); 
      return; 
     } 
     if (filterContext.Exception == null) 
     { 
      uow.Commit(); 
     } 
     else 
     { 
      try 
      { 
       uow.Rollback(); 
      } 
      catch(Exception ex) 
      { 
       // Do not let this new exception hide the original one. 
       Logger.Warn("Rollback failure on action failure. (If the" + 
        "transaction has been roll-backed on db side, this is" + 
        "expected.)", ex); 
      } 
     } 
    } 
} 

Für eine Schritt für Schritt Erklärung eines minimal MVC-Muster, diese große Blog-Serie von Ayende:

  1. Refactoring, baseline
  2. Refactoring, session scope
  3. Refactoring, broken
  4. Refactoring, view model
  5. Refactoring, globals
  6. Refactoring, transactions

Da meine IUnitOfWork einige spezielle Semantik hat, die mir in dem MVC-Muster, das ich mit NHibernate verwenden helfen, hier ist sie:

// This contract is not thread safe and must not be shared between threads. 
public interface IUnitOfWork 
{ 
    /// <summary> 
    /// Save changes. Generally unneeded: if a transaction is ongoing, 
    /// its commit does it too. 
    /// </summary> 
    void SaveChanges(); 
    void CancelChanges(); 
    bool HasActiveTransaction(); 
    void BeginTransaction(); 
    /// <summary> 
    /// Saves changes and commit current transaction. 
    /// </summary> 
    void Commit(); 
    void Rollback(); 
    /// <summary> 
    /// Encapsulate some processing in a transaction, committing it if 
    /// no exception was sent back, roll-backing it otherwise. 
    /// The <paramref name="action"/> is allowed to rollback the transaction 
    /// itself for cancelation purposes. (Commit supported too.) 
    /// Nested calls not supported (InvalidOperationException). If the 
    /// session was having an ongoing transaction launched through direct 
    /// call to <c>>BeginTransaction</c>, it is committed, and a new 
    /// transaction will be opened at the end of the processing. 
    /// </summary> 
    /// <param name="action">The action to process.</param> 
    void ProcessInTransaction(Action action); 
    /// <summary> 
    /// Encapsulate some processing in a transaction, committing it if 
    /// no exception was sent back, roll-backing it otherwise. 
    /// The <paramref name="function"/> is allowed to rollback the transaction 
    /// itself for cancellation purposes. (Commit supported too.) 
    /// Nested calls not supported (InvalidOperationException). If the 
    /// session was having an ongoing transaction launched through direct 
    /// call to <c>>BeginTransaction</c>, it is committed, and a new 
    /// transaction will be opened at the end of the processing. 
    /// </summary> 
    /// <param name="function">The function to process.</param> 
    /// <typeparam name="T">Return type of 
    /// <paramref name="function" />.</typeparam> 
    /// <returns>The return value of the function.</returns> 
    T ProcessInTransaction<T>(Func<T> function); 
} 

public class UnitOfWork : IUnitOfWork 
{ 
    private static readonly ILog Logger = 
     LogManager.GetLogger(typeof(UnitOfWork)); 

    private ISession Session; 

    public UnitOfWork(ISession session) 
    { 
     Session = session; 
    } 

    public void SaveChanges() 
    { 
     Session.Flush(); 
    } 

    public void CancelChanges() 
    { 
     Session.Clear(); 
    } 

    public bool HasActiveTransaction() 
    { 
     return Session.Transaction.IsActive; 
    } 

    public void BeginTransaction() 
    { 
     Session.BeginTransaction(); 
    } 

    public void Commit() 
    { 
     Session.Transaction.Commit(); 
    } 

    public void Rollback() 
    { 
     Session.Transaction.Rollback(); 
    } 

    public void ProcessInTransaction(Action action) 
    { 
     if (action == null) 
      throw new ArgumentNullException("action"); 
     ProcessInTransaction<object>(() => 
     { 
      action(); 
      return null; 
     }); 
    } 

    private bool _processing = false; 

    public T ProcessInTransaction<T>(Func<T> function) 
    { 
     if (function == null) 
      throw new ArgumentNullException("function"); 
     if (_processing) 
      throw new InvalidOperationException(
       "A transactional process is already ongoing"); 

     // Handling default transaction. 
     var wasHavingActiveTransaction = Session.Transaction.IsActive; 
     if (wasHavingActiveTransaction) 
      Commit(); 

     BeginTransaction(); 
     T result; 
     _processing = true; 
     try 
     { 
      result = function(); 
     } 
     catch 
     { 
      try 
      { 
       if(Session.Transaction.IsActive) 
        Rollback(); 
      } 
      catch (Exception ex) 
      { 
       // Do not let this new exception hide the original one. 
       Logger.Error("An additional error occurred while " + 
        "attempting to rollback a transaction after a failed " + 
        "processing.", ex); 
      } 
      // Let original exception flow untouched. 
      throw; 
     } 
     finally 
     { 
      _processing = false; 
     } 
     if (Session.Transaction.IsActive) 
      Commit(); 

     if (wasHavingActiveTransaction) 
      BeginTransaction(); 

     return result; 
    } 
} 
+0

Vielen Dank für die Verbesserung von ActionAttribute Ich möchte nicht jeden Datenzugriff einzeln abwickeln, deshalb enthält meine Berichtsbasis if (Session.Transaction! = null && Session.Transaction.IsActive) actionToExecute(); Aber ich würde gerne wissen, wie ich meine Transaktion verwalten und Lösung wie Castle Automatic Transaction Management oder andere erkunden – Arnaud

+0

Beachten Sie, dass Sie mit einem Transaktionsaktion Filter möglicherweise noch viele Transaktionen für eine Aktion haben, wenn Sie es brauchen. Mein Muster für diesen Fall besteht darin, die gerade laufende Transaktion zu committen, wenn ich eine Verarbeitung in einer eigenen Transaktion durchführen muss. Dann öffne ich ein neues, führe die Verarbeitung durch, übertrage es (oder Rollback bei einem Fehler) und öffne eine dritte Transaktion für den Rest der Aktion (und für meinen Standard-Transaktionsfilter, "glücklich" zu bleiben). Mein "IUnitOfWork" ist in der Tat eher ein "Einheit der Arbeit" als eine "Einheit der Arbeit". Ich habe es meiner Antwort hinzugefügt. –

+0

Ich mag die Idee, vielleicht implementieren Sie Ihre UoW mit DI-Interceptor in der Business-Schicht auf einige Methodenaufruf ist eine mögliche Lösung – Arnaud

2

I Behalte die aktuelle Sitzung als Eigenschaft in global.asax und öffne sie bei BeginRequest.

public static ISession CurrentSession 
    { 
     get { return (ISession)HttpContext.Current.Items[sessionkey]; } 
     set { HttpContext.Current.Items[sessionkey] = value; } 
    } 

    protected void Application_BeginRequest() 
    { 
     CurrentSession = SessionFactory.OpenSession(); 
    } 

    protected void Application_EndRequest() 
    { 
     if (CurrentSession != null) 
      CurrentSession.Dispose(); 
    } 

Dann habe ich ein Transaction-Attribut, mit dem Sie jede Controller-Aktion mit kennzeichnen können.

Und in meinem Repository habe ich eine Transaktionsmethode, die eine Transaktion startet, wenn gerade keine aktiv ist.

protected virtual TResult Transact<TResult>(Func<TResult> func) 
    { 
     if (_session.Transaction.IsActive) 
      return func.Invoke(); 

     TResult result; 
     using (var tx = _session.BeginTransaction(IsolationLevel.ReadCommitted)) 
     { 
      result = func.Invoke(); 
      tx.Commit(); 
     } 

     return result; 
    } 
+0

Hallo, Vielen Dank für Ihre Antwort, aber die Application_BeginRequest ist nicht Trigger auf Ressource (CSS ...) Anfrage ? – Arnaud

+0

Es sollte nicht in iis. – Fran

+0

Hallo, Nach ein paar Checks hast du Recht, aber ich bevorzuge neue Sitzung mit Autofac registrieren, weil es neue Sitzung InstancePerRequest() nur bei Bedarf auflöst (wenn Datenzugriff) – Arnaud