【问题标题】:Auto Dispose Sql Connections properly正确自动处理 Sql 连接
【发布时间】:2014-04-06 19:28:55
【问题描述】:

我的应用程序使用 3 层:DAL / Service / UL。

我的典型 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 内),我最终会得到许多打开的数据库连接或更糟。请告知最佳做法。我需要一种方法来自动处理我的数据库连接或任何其他非托管资源(文件等)。

【问题讨论】:

    标签: c# asp.net-mvc-4 dependency-injection


    【解决方案1】:

    你的问题又回到了所有权原则:

    拥有资源所有权的人应该处置它。

    虽然所有权可以转让,但您通常不应该这样做。在您的情况下,IDbConnection 的所有权从ordersService 转移到OrdersRepository(因为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 应用程序的 Global 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();
        }
    }
    

    当然,当您使用依赖注入框架时,这一切都会变得容易得多。例如,当您使用 Simple Injector(我是 Simple Injector 的主要开发人员)时,您可以将所有这些替换为以下几行代码:

    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 注册类型,这确保给定的实例在网络请求结束时被释放(就像我们在Application_EndRequest 中所做的那样)。通过调用RegisterMvcControllers,Simple Injector 将为您批量注册所有控制器。通过为 MVC 提供 SimpleInjectorDependencyResolver,我们允许 MVC 将控制器的创建路由到 Simple Injector(就像我们对控制器工厂所做的那样)。

    虽然此代码一开始可能有点难以理解,但当您的应用程序开始增长时,使用依赖注入容器变得非常有价值。 DI 容器将帮助您保持 Composition Root 的可维护性。

    【讨论】:

      【解决方案2】:

      好吧,如果你真的很想知道,你可以使用终结器(析构函数)在对象被垃圾回收时自动执行 Dispose。查看此链接,它基本上解释了您需要了解的有关 IDisposable 的所有信息,如果只想快速了解如何执行此操作,请跳至示例部分。终结器(析构函数)是 ~MyResource() 方法。

      但无论如何,您应该始终鼓励您的库的使用者正确使用 Dispose。通过垃圾收集器的自动清理仍然存在缺陷。你不知道垃圾收集器什么时候会完成它的工作,所以如果你实例化和使用大量这些类,然后在短时间内被遗忘,你可能仍然会遇到麻烦。

      【讨论】:

      • 我已经查看了 Finalizer,我注意到有时它没有在我的服务层对象中触发。我正在寻找一个更强大的解决方案,也许通过使用 Ioc 或 DI。但我以前从未使用过它们,也不知道从哪里开始。
      • 如果它没有被触发,那么你的对象没有被垃圾回收清理,你可能有内存泄漏。如果按照设计,某些实例会保留很长时间,那么也许您应该重新考虑如何使用它们。
      • 只有当类需要直接处理原生资源时,才应该在类中实现终结器。在这种情况下,实现终结器是没有用的,因为DbConnection 本身已经有终结器。
      • Steven 我应该坚持上面给出的方法还是您有什么建议?
      猜你喜欢
      • 1970-01-01
      • 2012-04-24
      • 1970-01-01
      • 1970-01-01
      • 2021-10-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多