07 Aug
2010

ASP.NET MVC Action und die Database Connection, nun richtig

 

Bei ASP.NET MVC ist es Best Practice wenn man für den Datenbank Zugriff eine Transaction beim Start der Action öffnet und zum Ende wieder schließt.

Ich habe die eine oder andere “Lösung” dazu gesehen, die sich jedoch meist in den Request-LifeCycle der Application einklinken und somit “irgendwie” außerhalb der MVC Konzeptes sind und somit auch beim Zugriff auf andere Seiten oder gar statischen Resourcen auch Connection/Transactionen öffnen. Auch ich nutzte dies … aber nun nicht mehr.

Die beste Lösung (jedenfalls aus meiner Sicht) ist ein eigener IActionInvoker der diese Aufgabe übernimm,. damit ist auch exakt den Gültigkeitsbereich der Transaktion definiert.

public interface IActionInvoker
{
    bool InvokeAction(ControllerContext controllerContext, string actionName);
}

In ASP.NET MVC ist es ein ActionInvoker der dafür sorgt dass die Action und das ActionResult so verarbeitet werden wie wir es vom Framework kennen. Die Standardimplementierung ist der ControllerActionInvoker. Der Controller selbst übernimmt die Erstellung  des IActionInvoker und zwar über die Methode CreateActionInvoker(). Diese ist virtuell und kann somit angepasst werden.

    protected virtual IActionInvoker CreateActionInvoker()
    {
      return new ControllerActionInvoker();
    }
Somit zu meiner Lösung. Ich habe einen IActionInvoker implementiert, der dafür sorge trägt dass der Aufruf der Action in in einer Datenbank-Transaction abläuft.

public class TransactionalActionInvoker : IActionInvoker
{
    private readonly IDatabaseTransaction transaction;
    private readonly IActionInvoker baseActionInvoker;

    public TransactionalActionInvoker(IDatabaseTransaction transaction,
                                        IActionInvoker baseActionInvoker)
    {
        this.transaction = transaction;
        this.baseActionInvoker = baseActionInvoker;
    }

    public bool InvokeAction(ControllerContext controllerContext, string actionName)
    {
        bool result;
        try
        {
            transaction.BeginTransaction();
            result = baseActionInvoker.InvokeAction(controllerContext, actionName);
        }
        catch (Exception)
        {
            transaction.ExceptionWhileTransaction();
            throw;
        }
        finally
        {
            transaction.EndTransaction();
        }
        return result;
    }
}
Als Abhängigkeiten drücke ich dem TransactionActionInvoker den eigentlichen IActionInvoker rein sowie eine IDatabaseTransaction zum Management des Transaktion. Ich gehe diesen Weg und leite nicht von ControllerActionInvoker ab damit beliebige Implementierungen von vorhandenen ActionInvokern verwendet werden können.

IDatabaseConnection ist ein eigenes Interface, darüber kann man die Transaction öffnen, und wieder schließen sowie mitteilen ob es im Ablauf der Action ein Problem gab.  Da die Implementierung Datenbankabhängig ist gehe ich hier nicht Groß darauf ein, nur der Hinweis das bei EndTransaction() bei einem Problem die Transaction mit einem Rollback() beendet wird sonst mit einem Commit().

public interface IDatabaseTransaction 
{
    void BeginTransaction();
    void EndTransaction();
    void ExceptionWhileTransaction();
}
In meinen Anwendungen habe ich immer einen Basis-Controller von dem ich alle meine Controller ableite. Ich hoffe Du machst dies auch so! Somit ist es sehr enfach die Erzeugung des IActionInvoker auszutauschen und den eigenen zu verwenden.

 

public abstract class TransactionController : Controller
{
    public IDatabaseTransaction Transaction { get; set; }

    protected override IActionInvoker CreateActionInvoker()
    {
        return new TransactionalActionInvoker(Transaction,base.CreateActionInvoker());
    }

    protected override void OnException(ExceptionContext filterContext)
    {
        base.OnException(filterContext);
        Transaction.ExceptionWhileTransaction();
    }
}

Nicht viel besonderes, es wird eine Instanz vom TransactionActionInvoker erzeugt und ihr die IDatabaseTransaction sowie der ursprüngliche IActionInvoker übergeben.

Wichtig ist es OnException() zu überschreiben um der IDatabaseTransaction mitzuteilen das eine Exception innerhalb der Action aufgetreten ist.

Die IDatabaseTransaction muss natürlich auch irgendwie in den Controller kommen, dies wird in diesem Fall vom IoC-Container gemacht und zwar per Property-Injection. Ich habe hier der Property-Injection gegenüber der Konstruktor Injection den Vorzug gegeben damit ich die die Abhängigkeit nicht bei jedem zu implementierenden Controller “manuell” bedienen muss. Dies kann man jedoch machen wie man möchte, ein Lookup über den Container oder IServiceLocator wäre auch möglich.

Den Lebenszyklus von IDatabaseTransaction habe ich über den IoC-Container auf pro HttpRequest festgelegt. Dies ist erforderlich, auch ist es erforderlich dass die eigenen Datenbank Session/Connection auf pro HttpRequest festgelegt ist. Es muss sichergestellt werden dass alle Datenbank Aufrufe innerhalb der Action mit derselben Connection bzw. Session aufgerufen werden.


Der Eintrag ist mir etwas Wert
 

Feedback

# re: ASP.NET MVC Action und die Database Connection, nun richtig

left by Thomas at 8/8/2010 1:42 AM Gravatar
Best Practice? Hab ich was verpasst?

Best Practice ist für mich seit es Connection Pooling gibt die Verbindung erst dann aufzumachen, wenn sie benötigt wird und sofort wieder zu schließen, wenn das nicht mehr der Fall ist. Und wenn das per Request 5x der Fall ist - auch egal, das Pooling regelt das.

# re: ASP.NET MVC Action und die Database Connection, nun richtig

left by Der Albert at 8/8/2010 11:42 AM Gravatar
Es geht um die Action, dass alles was in der Action passiert in einer Transaction passiert. Alles oder nichts.

Desweiteren ist es beim Einsatz vom OR/Ms wichtig dass man während der Action mit denselben Objekten und nicht mit den gleichen Objekten arbeitet. Dies kann man nur erreichen wenn man die Projekte von derselben Connection/Session erzeugt werden.

# re: ASP.NET MVC Action und die Database Connection, nun richtig

left by Der Albert at 8/8/2010 11:46 AM Gravatar
Halbschlaf am Sonntag morgen .. natürlich Objekte nicht Projekte

# re: ASP.NET MVC Action und die Database Connection, nun richtig

left by Thomas at 8/8/2010 9:32 PM Gravatar
Transactions für schreibende Zugriffe, ok. Schreib das doch gleich dazu ;-)
Comments have been closed on this topic.