c# - 正确自动处理 Sql 连接

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

最佳答案

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

he who has ownership of the resource, should dispose it.



虽然所有权可以转让,但您通常不应该这样做。在您的情况下,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 .

依赖注入(inject)框架可以帮助您使您的组合根可维护,但即使没有 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 中 Hook 您的自定义 Controller 工厂:
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();
    }
}

当然,当您使用依赖注入(inject)框架时,这一切都会变得容易得多。例如,当您使用 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 将为您批量注册所有 Controller 。通过为 MVC 提供 SimpleInjectorDependencyResolver我们允许 MVC 将 Controller 的创建路由到 Simple Injector(就像我们对 Controller 工厂所做的那样)。

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

关于c# - 正确自动处理 Sql 连接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22898873/

相关文章:

c# - 如何检查软键盘是否在设备上打开

c# - 覆盖 Autofac 注册 - 使用 DI 进行集成测试

c# - UnityContainer - 如何使用依赖链注册/解析

c# - 错误 : Azure. Messaging.EventHubs : The path to an Event Hub may be specified as part of the connection string or as a separate value, 但不是两者

c# - 绘制圆时如何控制圆的大小和位置

c# - 如何添加smtp hotmail账号发送邮件

asp.net-mvc-4 - 在 MVC 4 的下拉列表中选择一个默认值

mysql - 如何在 mvc4 代码优先应用程序中使用 MySQL 成员资格?

jquery - 不引人注目的验证不适用于 MVC 4 中的 Textarea

c# - 我应该在带有 Unity 容器的控制台应用程序中的何处注册解析我的类型?