正确自动处理Sql连接

时间:2014-04-06 19:28:56

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

我的应用程序使用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中),最终会出现许多开放式数据库连接或更糟糕的情况。请告知最佳做法。我需要一种方法来自动处理我的数据库连接或任何其他非托管资源(文件等)。

2 个答案:

答案 0 :(得分:10)

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

  拥有资源所有权的人应该将其处置掉。

虽然可以转让所有权,但通常不应该这样做。在您的情况下,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应用程序的全局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注册类型,这可确保在Web请求结束时处理给定实例(就像我们在Application_EndRequest中所做的那样)。通过调用RegisterMvcControllers,Simple Injector将为您批量注册所有控制器。通过向MVC提供SimpleInjectorDependencyResolver,我们允许MVC将控制器的创建路由到Simple Injector(就像我们对控制器工厂所做的那样)。

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

答案 1 :(得分:0)

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

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