ASP.NET MVC のスキャフォールディングを使うと、以下のように Entity Framework の DbContext をコントローラのフィールドに持つコードが生成されます。
public class CustomerController : Controller { private SampleDbContext db = new SampleDbContext(); // // GET: /Customer/ public ActionResult Index() { return View(db.Customers.ToList()); } }
単純なアプリの場合はこれでいいかもしれないですが、リポジトリパターンを使った場合では DbContext をどうやって持つべきかと悩む人が居ると思います。
変更追跡やトランザクションの観点的に、DbContext や Unit of Work は複数作成するべきではないですよね。
public class CustomerRepository : ICustomerRepository { // インスタンスはどうする? private SampleDbContext _db; public Customer FindById(int id) { return db.Customers.Find(id); } }
Unity などの DI コンテナを使えば 1 リクエストごとに 1 インスタンスだけ作成されるように出来ます。*1
しかし、DI コンテナを使うのに適さない、小~中規模のケースもあると思います。そういう場合のために、HttpContext.Items プロパティを使って DbContext のインスタンスを、1 リクエストごとに 1 インスタンスだけ作成されるようにしましょう。
public class SampleDbContext : DbContext { private const string CacheKey = "__SampleDbContext__"; public static bool HasCurrent { get { return HttpContext.Current.Items[CacheKey] != null; } } public static SampleDbContext Current { get { var context = (SampleDbContext)HttpContext.Current.Items[CacheKey]; if (context == null) { context = new SampleDbContext(); HttpContext.Current.Items[CacheKey] = context; } return context; } } }
これで SampleDbContext.Current プロパティを参照すると、そのリクエストで DbContext が作成されていなければ自動で作成されますし、作成されている場合はそれが使われます。
ちなみに、このままだと作成した DbContext の Dispose が呼ばれないので、Global.asax.cs に Application_EndRequest を追加して、その内部で DbContext の存在を確認して Dispose 呼び出すようにします。
public class MvcApplication : System.Web.HttpApplication { protected void Application_EndRequest() { if (SampleDbContext.HasCurrent) { SampleDbContext.Current.Dispose(); } } }
これで DbContext を 1 リクエストに 1 つだけ生成されるようになりましたし、Dispose の処理も問題ないですね。複数リポジトリにも Current プロパティを参照することで簡単に対応出来るようになりました。
*1:PerRequestLifetimeManager を使ったりする