在注入的仓库上实现IDisposable

8
我有以下的 ADO .Net Repository。
public class Repository : IRepository, IDisposable
{
   private readonly IUnitOfWork UnitOfWork;
   private SqlConnection Connection;

   public Repository(IUnitOfWork unitOfWork, connectionString)
   {
      UnitOfWork = unitOfWork;
      Connection = new SqlConnection(connectionString);
      Connection.Open();
   }

   public MyObject FindBy(string userName)
   {
      //...Ado .Net command.ExecuteReader, etc.
   }
}

这个代码库中使用了一个IoC容器来注入到一个领域服务中,并且如下使用:

public class UserDomainService : IUserDomainService
{
   private readonly IRepository Repository;

   public UserDomainService(IRepository repository)
   {
      Repository = repository;
   }

   public User CreateNewUser(User user)
   {
      using(Repository)
      {
         var user = Repository.FindBy(user.UserName);
         if(user != null)
            throw new Exception("User name already exists!");

         Repository.Add(user);
         Repository.Commit();
      }
   }
}

这里的想法是,我总是将Repository对象放入using语句中,这样当它完成时,连接就关闭并被处理了,但我认为这是个问题,因为Domain Service类仍然存在,如果有第二个调用它的情况,它将失败,因为repository已经被销毁了。
现在我完全控制所有的代码,我只想设计粗粒度的服务调用,但整个事情似乎不太对。
我这样做是为了避免Domain Service知道Repository中的OpenConnection和CloseConnection方法。
这种设计本质上是不好的,还是有更好的方法吗?
思考之后:当请求到达时,所有的依赖树都是在WCF层生成的,当然你可以看到连接在那一刻打开,因为它发生在repository的构造函数中,所以我认为这并不是很糟糕,因为它仅在此特定调用期间打开。这个假设是正确的吗?或者我在过早地打开DB连接时做了什么可怕的事情?

IRepository 是否与 Repository 紧密耦合?也就是说,它是否包含了 Find 等方法?如果是这样,那么这个接口是否意味着实现 IDisposable 接口? - Lasse V. Karlsen
我有自己的问题可能与此相关:ServiceContainer、IoC和可处理对象 - Lasse V. Karlsen
1
为什么你需要在Repository中使用SqlConnection?这似乎更适合于你的IUnitOfWork - Steven
2个回答

11

注入一个工厂来创建所需的实例,而不是实例本身。

使用 IRepositoryFactory 来创建 IRepository 并在每次使用后将其释放。这样,领域服务或工厂都不需要是可释放的。另外,很重要的是,通过仍然注入实现而不是硬编码它,你可以保持代码的抽象性。

public class UserDomainService : IUserDomainService
{
   private readonly IRepositoryFactory RepositoryFactory;

   public UserDomainService(IRepositoryFactory factory)
   {
      RepositoryFactory = factory;
   }

   public User CreateNewUser(User user)
   {
      using (IRepository repository = RepositoryFactory.Create())
      {
         var user = repository.FindBy(user.UserName);
         if(user != null)
            throw new Exception("User name already exists!");

         repository.Add(user);
         repository.Commit();
      }
   }
}

你不必总是注入你需要的类型。通过阅读 Castle Windsor(其思维方式是注册-解析-释放),你会发现如果想在应用程序生命周期中的任何时候解析对象,建议使用类型工厂。

你知道你将需要一个存储库,但却不知道 何时 需要它。与其请求一个存储库,不如请求 创建 存储库的东西。这样就保持了抽象层级,并且没有泄漏任何实现细节。


我真是太蠢了,不知道为什么没想到这个。谢谢。 - Sergio Romero
@SergioRomero 有时候你需要退后一步,与他人讨论问题,然后才能在自己得出正确结论之前找到问题的答案。我经常这样做,如果你一直深陷其中,很容易陷入设计困境 :-( - Adam Houldsworth
你现在有一个泄漏的抽象层。违反了封装原则。服务不需要知道存储库的生命周期。你揭示了秘密。最好的解决方案是重写存储库,使其为每个事务自己打开和关闭连接。 - Dmitriy Startsev
是的和不是的,某个地方会需要知道它需要被处理。也许存储库泄漏了 IDisposable。归根结底,存储库的公共契约需要被处理,我不认为这是泄漏的。 - Adam Houldsworth

1
你遇到的问题是所有权的问题。 UserDomainService 类并未创建 IRepository,但是它仍然拥有该实例的所有权,因为它对其进行了处理。
一般规则是创建对象的人应该销毁它。换句话说,创建对象的人就是所有者,所有者应该销毁该对象。
你有两个解决方案:
1. 创建一个 IRepositoryFactory,像 Adam 解释的那样。这样的工厂上的 CreateNewRepository() 方法将清楚地传达调用者获得所有权并应该处理所创建的存储库的实例。 2. 让创建(和注入)存储库的人处理存储库的处理。无论是在 WCF 服务中手动执行此操作,还是使用 IoC/DI 框架。如果你使用 DI 框架,则可能需要查看 Per Web Request 生命周期或类似的内容。
最后一点,你的 IRepository 实现了 IDisposable。选择方案2时,可以从 IRepository 中移除 IDisposable 接口,这样就可以隐藏应用程序中涉及资源的事实。从应用程序中隐藏 IDisposable 是一件好事,因为该接口是一个漏洞抽象。你已经遇到过这个问题,因为在应用程序内部调用 Dispose 会导致整个应用程序崩溃。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接