2014-04-06 43 views
6

我的應用程序使用3層:DAL/Service/UL。自動處理適當的Sql連接

我的典型DAL類看起來是這樣的:

public class OrdersRepository : IOrdersRepository, IDisposable 
{ 
    private IDbConnection _db; 

    public OrdersRepository(IDbConnection db) // constructor 
    { 
     _db = db; 
    } 

    public void Dispose() 
    { 
     _db.Dispose(); 
    } 
} 

我的服務調用DAL類這樣的(注射數據庫連接):

public class ordersService : IDisposable 
{ 
    IOrdersRepository _orders; 

    public ordersService() : this(new OrdersRepository(new Ajx.Dal.DapperConnection().getConnection())) 
    { 
    } 

    public ordersService(OrdersRepository ordersRepo) 
    { 
     _orders = ordersRepo; 
    } 

    public void Dispose() 
    { 
     _orders.Dispose(); 
    } 
} 

然後終於我的UI層內,這是我如何訪問我的服務層:

public class OrdersController : Controller, IDisposable 
{ 
    // 
    // GET: /Orders/ 
    private ordersService _orderService; 

    public OrdersController():this(new ordersService()) 
    { 
    } 

    public OrdersController(ordersService o) 
    { 
     _orderService = o; 
    } 

    void IDisposable.Dispose() 
    { 
     _orderService.Dispose(); 
    } 
} 

這一切工作良好。但正如你所看到的,我依靠每層中的IDisposable。 UI配置服務對象然後服務對象配置DAL對象然後DAL對象配置數據庫連接對象。

我確信必須有更好的方法來做到這一點。恐怕用戶可能忘記處理我的服務對象(在UI中),並且我將以許多開放的數據庫連接結束,或者更糟。請告知最佳做法。我需要一種方法來自動處理我的數據庫連接或任何其他非託管資源(文件等)。

回答

10

你的問題又回到了當家作主的原則:

他誰擁有資源的所有權,應釋放它。

雖然所有權可以轉讓,但您通常不應該這樣做。在你的情況IDbConnection的所有權從ordersServiceOrdersRepository(因爲OrdersRepository部署連接)轉移。但在很多情況下,OrdersRepository無法知道連接是否可以處置。它可以在整個對象圖中重用。所以一般來說,你不應該通過構造函數處理傳遞給你的對象。

另一件事是,依賴的往往是消費者無法知道,如果一個依賴需要處理,因爲無論是否依賴需要處置是一個實現細節。該信息可能在界面中不可用。

所以取而代之,重構你的OrdersRepository以下幾點:

public class OrdersRepository : IOrdersRepository { 
    private IDbConnection _db; 

    public OrdersRepository(IDbConnection db) { 
     _db = db; 
    } 
} 

由於OrdersRepository沒有取得所有權,IDbConnection並不需要處置IDbConnection,你不需要實現IDisposable。這明確地將處理連接的責任移至OrdersService。但是,ordersService本身並不需要IDbConnection作爲依賴;它只取決於IOrdersRepository。那麼,爲什麼不動建立對象圖出OrdersService以及共同的責任:

public class OrdersService : IDisposable { 
    private readonly IOrdersRepository _orders; 

    public ordersService(IOrdersRepository ordersRepo) { 
     _orders = ordersRepo; 
    } 
} 

由於ordersService無關處置本身,沒有必要實施IDisposable。而且由於它現在只有一個構造函數可以接受它所需的依賴關係,所以該類更容易維護。

因此,這將創建對象圖的職責移動到OrdersController。但是,我們應該採用同樣的模式在OrdersController還有:

public class OrdersController : Controller { 
    private ordersService _orderService; 

    public OrdersController(ordersService o) { 
     _orderService = o; 
    } 
} 

再次,這個類已經變得更加容易把握,並沒有需要配置什麼,因爲它不具有或把所有權任何資源。

當然,我們只是移動並推遲了我們的問題,因爲顯然我們仍然需要創建我們的OrdersController。但不同之處在於,我們現在將構建對象圖的責任轉移到應用程序中的一個地方。我們稱這個地方爲Composition Root

依賴注入框架,可以幫助你讓你的成分根維護,但即使沒有一個DI框架,通過實現自定義ControllerFactory建立你的對象圖在MVC很簡單:

public class CompositionRoot : DefaultControllerFactory { 
    protected override IController GetControllerInstance(
     RequestContext requestContext, Type controllerType) { 
     if (controllerType == typeof(OrdersController)) { 
      var connection = new Ajx.Dal.DapperConnection().getConnection(); 

      return new OrdersController(
       new OrdersService(
        new OrdersRepository(
         Disposable(connection)))); 
     } 
     else if (...) { 
      // other controller here. 
     } 
     else { 
      return base.GetControllerInstance(requestContext, controllerType); 
     } 
    } 

    public static void CleanUpRequest() } 
     var items = (List<IDisposable>)HttpContext.Current.Items["resources"]; 
     if (items != null) items.ForEach(item => item.Dispose()); 
    } 

    private static T Disposable<T>(T instance) 
     where T : IDisposable { 
     var items = (List<IDisposable>)HttpContext.Current.Items["resources"]; 
     if (items == null) { 
      HttpContext.Current.Items["resources"] = 
       items = new List<IDisposable>(); 
     } 
     items.Add(instance); 
     return instance; 
    } 
} 

你可幫您的自定義控制器工廠在你的MVC應用程序的這樣的全球ASAX:

public class MvcApplication : System.Web.HttpApplication 
{ 
    protected void Application_Start() 
    { 
     ControllerBuilder.Current.SetControllerFactory(
      new CompositionRoot()); 
    } 

    protected void Application_EndRequest(object sender, EventArgs e) 
    { 
     CompositionRoot.CleanUpRequest(); 
    } 
} 

當然,當您使用依賴注入框架這一切變得更加容易。例如,當您使用簡單的注射器(我是簡單的噴油器的鉛DEV),你可以用下面的代碼幾行替換這一切:

using SimpleInjector; 
using SimpleInjector.Integration.Web; 
using SimpleInjector.Integration.Web.Mvc; 

public class MvcApplication : System.Web.HttpApplication 
{ 
    protected void Application_Start() 
    { 
     var container = new Container(); 

     container.RegisterPerWebRequest<IDbConnection>(() => 
      new Ajx.Dal.DapperConnection().getConnection()); 

     container.Register<IOrdersRepository, OrdersRepository>(); 
     container.Register<IOrdersService, OrdersService>(); 

     container.RegisterMvcControllers(Assembly.GetExecutingAssembly()); 

     container.Verify(); 

     DependencyResolver.SetResolver(
      new SimpleInjectorDependencyResolver(container)); 
    } 
} 

有一些有趣的東西,怎麼回事上面的代碼。首先,Register的調用告訴Simple Injector他們需要返回一個特定的實現,當請求給定的抽象時,應該創建它。接下來,Simple Injector允許使用Web Request Lifestyle註冊類型,它確保在Web請求結束時處理給定實例(就像我們在Application_EndRequest中那樣)。通過致電RegisterMvcControllers,Simple Injector將爲您批量註冊所有控制器。通過向MVC提供SimpleInjectorDependencyResolver,我們允許MVC將控制器的創建路由到簡單注入器(就像我們在控制器工廠那樣)。

雖然這段代碼起初可能有點難以理解,但當您的應用程序開始增長時,使用依賴注入容器變得非常有價值。 DI容器將幫助您保持Composition Root可維護。

0

那麼,如果你真的偏執於那個,你可以使用終結器(析構函數)在對象被垃圾收集時自動執行Dispose。檢查這個鏈接,基本上解釋你需要知道的關於IDisposable的所有內容,如果你想要一個快速示例如何做到這一點,請跳至示例部分。終結器(析構函數)是〜MyResource()方法。

但是,無論如何,您應該始終鼓勵您的圖書館消費者正確使用Dispose。通過垃圾收集器自動清理仍然存在缺陷。你不知道垃圾收集器什麼時候可以完成它的工作,所以如果你有很多這樣的類被實例化和使用,那麼很快就會被遺忘,你可能仍然會陷入困境。

+0

我已經看了Finalizer,並且我注意到它有時並沒有在我的服務層對象中激發。我正在尋找更強大的解決方案,可能通過使用Ioc或DI。但我從來沒有用過它們,也不知道從哪裏開始。 – highwingers

+0

如果它沒有觸發,那麼你的對象不會被垃圾回收清除,你可能有一個內存韭菜。如果通過設計某些實例,保留很長時間,那麼也許你應該重新思考你如何使用它們。 –

+1

如果該類需要直接處理本機資源,則只應在類中實現終結器。在這種情況下,實現一個終結器是無用的,因爲'DbConnection'本身已經有一個終結器。 – Steven