ASP.NET MVC の Controller で Entiry Framework のトランザクション制御をする

目的

  • Entity Framework を使ってDBへのアクセスを行いたい
  • ASP.NET MVC の Controllerを継承したクラスを作成して TransactionScope の制御をしたい

Entity Framework のトランザクション管理

今回は、アクションの実行単位をトランザクションの作成単位にしようとします。Entity FrameworkはDbContext内でDBへの接続等を行なっており、TransactionScopeが既にあればそのトランザクションを利用する仕様です。*1

となるとASP.NET MVC でのトランザクション制御の流れは以下のとおりになります。

正常系
  1. アクション実行前にトランザクション開始
  2. 更新系処理実行
  3. アクション実行後にコミット
異常系
  1. アクション実行前にトランザクション開始
  2. 更新系処理実行
  3. アクション実行中に例外
  4. ロールバック

Controller の Action 実行後にトランザクションを閉じるべき場所は?

ASP.NET MVC の Controller には Action実行後にに呼び出されるメソッドをがいくつかあります。

  • アクションの実行後に実行される OnActionExecutedメソッド
  • アクションでの例外発生時に実行されるOnExceptionメソッド
  • アクションの結果が処理される直前に実行されるOnResultExecutingメソッド

実行される順序もそうですが、例外発生時に呼び出されるのか検証してみました

実験:OnActionExecutedとOnException どっちが先に呼ばれるか

 public class BaseController : Controller
    {
        protected override void OnException(ExceptionContext filterContext)
        {
            base.OnException(filterContext);
        }

        protected override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            base.OnActionExecuted(filterContext);
        }

        protected override void OnResultExecuting(ResultExecutingContext filterContext)
        {
            base.OnResultExecuting(filterContext);
        }
    }

上の BaseController を継承したクラス。

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Hoge.Models;

namespace Hoge.Controllers
{
    public class StaffController : BaseController
    {
        private HogeContext db = new HogeContext();

        [HttpPost]
        public ActionResult Create(Staff staff)
        {
            if (ModelState.IsValid)
            {
                db.Staffs.Add(staff);
                db.SaveChanges();
                throw new Exception("検証用です");
                return RedirectToAction("Index");
            }

            return View(staff);
        }
    }
}

これでStaffContollerのCreateが実行してみました。

結果

実行順序は、OnActionExecuted→OnException でした。OnResultExecutingは呼び出されずにStaffControllerの Dispose が実行されました。

結論

TransactionScopeのインスタンス化をOnActionExecuting で行い、OnActionExecuted 内でコミット(TransactionScope.Completeを呼ぶ)。例外発生時は、ロールバック(Transaction.Disposeを呼ぶ)する実装で行こうかと思ってます。OnActionExecuted 内で例外が発生しているかしていないかは、ActionExecutedContextのExceptionプロパティで判断できます。例外が発生していないonExceptionメソッドが実行されない場合は、ActionExecutedContextのインスタンスのExceptionプロパティはnullです。

*1: http://msdn.microsoft.com/ja-jp/library/bb896325.aspx:title