正确自动处理Sql连接

hig*_*ers 6 c# dependency-injection asp.net-mvc-4

我的应用程序使用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();
    }
}
Run Code Online (Sandbox Code Playgroud)

我的服务像这样调用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();
    }
}
Run Code Online (Sandbox Code Playgroud)

最后在我的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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这一切都很好.但正如你所看到的,我依赖IDisposable于每一层.UI配置服务对象然后服务对象配置DAL对象然后DAL对象配置数据库连接对象.

我相信必须有更好的方法.我担心用户可能会忘记处理我的服务对象(在UI中),最终会出现许多开放式数据库连接或更糟糕的情况.请告知最佳做法.我需要一种方法来自动处理我的数据库连接或任何其他非托管资源(文件等).

Ste*_*ven 10

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

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

虽然可以转让所有权,但通常不应该这样做.在您的情况下,所有权IDbConnection从转移ordersServiceOrdersRepository(因为OrdersRepository处置连接).但在许多情况下,OrdersRepository无法知道连接是否可以处理.它可以在整个对象图中重用.因此,通常,您不应该通过构造函数处理传递给您的对象.

另一个问题是依赖关系的使用者通常无法知道依赖关系是否需要处理,因为是否需要处理依赖关系是实现细节.该信息可能在界面中不可用.

所以相反,重构你OrdersRepository的以下内容:

public class OrdersRepository : IOrdersRepository {
    private IDbConnection _db;

    public OrdersRepository(IDbConnection db) {
        _db = db;
    }
}
Run Code Online (Sandbox Code Playgroud)

由于OrdersRepository不占有,IDbConnection不需要配置IDbConnection而您不需要实施IDisposable.这明确地将处理连接的责任移到了OrdersService.但是,ordersService它本身并不需要IDbConnection作为依赖; 它只取决于IOrdersRepository.那么为什么不将构建对象图的责任移出OrdersService:

public class OrdersService : IDisposable {
    private readonly IOrdersRepository _orders;

    public ordersService(IOrdersRepository ordersRepo) {
        _orders = ordersRepo;
    }
}
Run Code Online (Sandbox Code Playgroud)

由于ordersService无需自行处理,因此无需实施IDisposable.而且由于它现在只有一个构造函数来获取它所需的依赖项,因此该类变得更容易维护.

因此,这将创建对象图的责任转移到了OrdersController.但我们应该同样应用相同的模式OrdersController:

public class OrdersController : Controller {
    private ordersService _orderService;

    public OrdersController(ordersService o) {
        _orderService = o;
    }
}
Run Code Online (Sandbox Code Playgroud)

同样,这个类变得更容易掌握,并且它不需要处理任何东西,因为它没有或拥有任何资源.

当然,我们只是移动并推迟了我们的问题,因为显然我们仍然需要创建我们的问题OrdersController.但不同之处在于,我们现在将构建对象图的责任转移到应用程序中的单个位置.我们称这个地方为组合根.

依赖注入框架可以帮助您使组合根可维护,但即使没有DI框架,您也可以通过实现自定义在MVC中轻松构建对象图ControllerFactory:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以将自定义控制器工厂挂钩到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();
    }
}
Run Code Online (Sandbox Code Playgroud)

当然,使用依赖注入框架时,这一切都变得更加容易.例如,当您使用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));
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的代码中有一些有趣的事情发生了.首先,在Register请求给定抽象时,应该创建告诉Simple Injector它们需要返回某个实现的调用.接下来,Simple Injector允许使用the注册类型Web Request Lifestyle,这确保在Web请求结束时处理给定实例(就像我们在其中所做的那样Application_EndRequest).通过调用RegisterMvcControllers,Simple Injector将为您批量注册所有控制器.通过提供MVC,SimpleInjectorDependencyResolver我们允许MVC将控制器的创建路由到Simple Injector(就像我们对控制器工厂所做的那样).

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