【问题标题】:Is it bad practice to have a repository that manually opens and closes a db connection?拥有一个手动打开和关闭数据库连接的存储库是不好的做法吗?
【发布时间】:2012-02-13 19:28:25
【问题描述】:

环境:ASP.NET MVC3 C#

假设我有一些存储库(半伪):

public interface IRepository
{
 create();read();update();delete();opendb();closedb();
}

public class CarRepository : IRepository
{
 private DbContext namedDbContext;

 public void opendb()
 {
  namedDbContext = new DbContext();
 }
 public void closedb()
 {
  namedDbContext.dispose();
 }
}

然后在控制器中注入存储库并按如下方式使用以手动控制数据库连接生存期:

public class SomeController : Controller
{
    private IRepository CarRepository;

    public void SomeController(IRepository _carRepository)
    {
        CarRepository = _carRepository;
    }

    public ActionResult SomeAction(int CarId)
    {
        CarRepository.opendb();
        var car = CarRepository.read(CarId);
        CarRepository.closedb();
    }
}

这是否被认为是不好的做法,因为它从存储库控制连接并将其放置在控制器中?我担心使用依赖注入会导致内存泄漏,并希望确保不会打开重复的连接,也不会长时间运行和未使用。

【问题讨论】:

  • 这种手动控制的优势是什么?有一些明显的缺点,但我几乎看不到任何优点。
  • 这听起来像是对未知的恐惧。您应该直接使用 IoC 容器并玩得开心。 Install-Package Autofac.Mvc3
  • mvc3 框架使用了没有 dispose 方法的 IDependencyResolver,我正在考虑使用手动控制至少连接以确保没有内存泄漏。
  • IDependencyResolver 只是一个服务定位器合约。由每个实施者决定如何清理资源。
  • 看起来这可能是一个准备好的射击瞄准情况:(我想我会去装满水桶并寻找漏洞。

标签: c# asp.net-mvc-3 datacontext object-lifetime


【解决方案1】:

是的。当然。大多数 ADO.NET 驱动程序都使用连接池,因此实际的连接过程并没有那么繁重。你有TransactionScope,它可以处理多个连接上的事务,但它不会像一个连接上的一个事务那么快。

我担心使用依赖注入会导致内存泄漏,并希望确保不会打开重复的连接,也不会长时间运行和未使用。

IoC 将保证清理连接(大量用户群已确保这一点)。不能保证程序员会在所有地方进行清理。

【讨论】:

  • 我在控制器工厂中使用 Unity。所以你不认为嵌套依赖会成为 Unity 的内存泄漏(或一般的主流 DI 容器)?
  • +1 表示“程序员不会”。哈哈。我们是人类还是舞者?
  • @TravisJ:取决于你如何使用 Unity。您必须进行适当的清理。安装nuget包unity.mvc3或者查看源码。
  • @jgauffin - 你提到范围,TransactionScope 是 Unity 框架的一部分吗?你知道我在哪里可以找到他们的清单吗?
  • TransactionScope 是 .NET 4 中的一个新类,位于 System.Transactions
【解决方案2】:

资源库模式提供了持久层的抽象。它不应该暴露任何持久性细节,例如数据库连接。如果存储是xml文件,还是云存储呢?

所以是的,这是不好的做法。如果您想要更多控制,您可以让存储库使用工作单元模式,以便更高级别应该决定何时提交事务,但仅此而已。存储库不应公开数据库的任何知识。

对于内存泄漏,使存储库实现 IDIsposable(关闭任何未完成的打开连接)并确保 DI 容器管理每个请求的存储库实例,它将对其调用 Dispose。

【讨论】:

  • 我担心的是,有时在 mvc3 框架中,当对数据上下文对象进行浅拷贝时,它会强制连接保持活动状态。 IDisposable 是否比 using(){} 更可靠?
  • using 语句首先需要 IDisposable。不用管 EF 做什么,任何 orm 都会尽快关闭 db 连接。
  • 不幸的是,情况似乎并非如此,尤其是嵌套依赖项。
  • 当我使用 orm 时,通常是 Nhibernate,我的流程通常是这样的:我已将 autofac 配置为获取一个 ISession 实例(相当于 DbContext)和一个存储库实例请求,ISession 被注入存储库。 Nhibernate 会尽快关闭数据库连接,它不会等待 ISession 被释放,并且 autofac 在请求结束时自动调用 ISession 和 Repository 上的 dispose。那里没有内存泄漏和依赖问题。继续 ->
  • 如果您确实需要将 DBContext 作为依赖项传递,也许您应该在请求结束时将其释放一次。
【解决方案3】:

存储库的一部分正在抽象出持久性的细节。

我发现您的提案存在两个问题:

  1. 通过将这些方法命名为“opendb”和“closedb”,您泄露了不必要的抽象,并且
  2. 如果你走这条路,你应该从opendb()方法返回IDisposable(连接对象),并将动作包装在using块中,以确保连接关闭。

通常,您可以让存储库为每个方法创建一个连接,因此您只需在存储库方法中正确设置即可。当您想要对存储库执行多个操作而不为每个部分使用单独的连接时,挑战就来了。

要实现这一点,您可以从存储库中公开工作单元的概念。您的工作单元将实现存储库方法的接口,因此您不能在工作单元之外调用它们。它还将实现IDisposable,因此每当您调用存储库时,您将使用using 块。在内部,存储库将管理连接,但既不会公开连接,也不会“谈论它”。

例如:

public ActionResult SomeAction(int CarId)
{
     using (var repo = CarRepository.BeginUnitOfWork())
     {
        var car = repo.read(CarId);
        // do something meaningful with the car, do more with the repo, etc.
     }
}

【讨论】:

  • UnitOfWork 模式通常不包含存储库作为依赖项吗?
  • @TravisJ 是的,这不是工作单元模式实现,也不是事务性的。不过,我正在为一个更好的名字而奋斗。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-31
  • 2017-02-01
  • 1970-01-01
  • 2016-12-28
  • 2011-05-16
相关资源
最近更新 更多