2014年8月1日 星期五

管理 ASPNET MVC 中的 Entity Framework DbContext 的生命週期

在應用程式中管理 DbContext 實例是非常重要的, 一個 DbContext 使用了資料庫連線(database connections)這樣重要的資源且需要被釋放(released), 如果沒有正確的 dispose 一個 DbContext 實例, 那麼相關的資料庫連線可能不會被釋放回連線池(connection pool).
寫老式的ADO.NET程式的人都知道一定要這麼做 !

在ASP.NET MVC應用程式中, DbContext  實例基本上是在 Controller 中使用, 一些基礎知識說明 MVC 的 controllers 在當請求(request)到達時被建立, 然後在請求已經完成時被 dispose (are disposed), 在 ASP.NET MVC中如何確認 DbContext   實例被 dispose (is disposed)?



方法1 : 使用 Using 區塊

using (EmployeeContext context = new EmployeeContext())
{
    return View(context.Employees.ToList());
}

使用 using 區塊可以確保, 當執行到區塊底部時, EmployeeContext 會被 dispose (is disposed), using 區塊是 try{...}finally{...} 的簡寫方式(語法糖), context (DbContext 實例)會在 finally 裡面被 dispose (is disposed), 這區塊會確保 context 被 dispose (is disposed), 但是這樣很難在應用程式中的不同地方分享同一個 context, 會發現自己一直在建立更多要使用的 DbContext 實例, 這也使自己的 Controller 裡出現邏輯處理之外的雜訊, 即使比使用 try finally 乾淨多了, 但是依然感覺一些雜訊存在



方法2 : Dispose 區塊

另一種方法是在 controller 中實作 dispose

 public class EmployeeController : Controller

{
    private EmployeeContext _context;
 
    public EmployeeController()
    {
        _context = new EmployeeContext();
    }
        
    public ActionResult Index()
    {
        return View(_context.Employees.ToList());
    }
        
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _context.Dispose();
        }
        base.Dispose(disposing);
    }
}

當請求完成時, controller 會被dispose (is disposed), 這方式也確保了 EmployeeContext 也被dispose (is disposed); 和 using 區塊很像, 在Controller裡產生了雜訊並且很難在應用程式中不同的地方分享同一個實例, 這方法並且依賴團隊中的所有開發人員要正確的實作 dispose 

方法3 : 依賴注入 (Dependency Injection)

public class EmployeeController : Controller
{
    private EmployeeContext _context;
 
    public EmployeeController(EmployeeContext context)
    {
        _context = context;
    }
        
    public ActionResult Index()
    {
        return View(context.Employees.ToList());
    }
}

這方法解除了 controller 對 DbContext 實例的生命週期的責任. controller 要求一個 DbContext 實例, 但是不需要關心這實例從哪裡來或是當她結束的時候會去那裡?
我們知道這個 Controller 中只有一個建構子, 所以建立 EmployeeController 必須傳入一個 EmpolyeeContext 實例, 所以 Controller 不用再負責建立 DbContext, 意思說也不再需要去 dispose !
但是如果 Controller 不用再去建立 context, 那誰該去建立? 我們如何確認 context 真的被 dispose (is being disposed)?
用 IoC 容器解決這問題!
nuget上有很多 injection/IoC 容器, 例如 NInject, 大致流程是註冊使用 NInject 的 OnPerRequestHttpModule 並設置要用來產生實例的 DbContext(例如範例中的 EmpolyeeContext), 經過這些設置後, NInject 將會認出你的 Controller 所要求的實例(例如 EmpolyeeContext 的實例), 然後執行以下流程:
1. 每次 Http Request 時建立實例
2. 傳送實例到 Controller 建構子
3. Http Request 結束時 dipose 實例
OnPerRequestHttpModule 的預設行為:每次 Http Request 都建立一個新的 EmpolyeeContext 實例(context), 這表示不同的 Request 無法使用同一個 context, 也保證不會產生兩個以上的 EmpolyeeContext 被建立, 即使最後請求經過了三個都要求同一個 EmpolyeeContext 的 Controllers, 換句話說, context 的生命週期和 request 的生命週期綁在一起


沒有留言: