c# – 存储库模式 – 使其可测试,DI和IoC友好和IDisposable

前端之家收集整理的这篇文章主要介绍了c# – 存储库模式 – 使其可测试,DI和IoC友好和IDisposable前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
是)我有的:
public interface IRepository
{
   IDisposable CreateConnection();
   User GetUser();
   //other methods,doesnt matter
}

public class Repository
{
   private sqlConnection _connection;

   IDisposable CreateConnection()
   {
      _connection = new sqlConnection();
      _connection.Open();
      return _connection;
   }

   User GetUser()
   {
      //using _connection gets User from Database
      //assumes _connection is not null and open
   }
   //other methods,doesnt matter 
}

这使得使用IRepository的类可以轻松测试并且IoC容器友好.但是,使用此类的人必须在调用数据库获取内容的任何方法之前调用CreateConnection,否则将抛出异常.这本身就很好 – 我们不希望在应用程序中有持久的联系.所以使用这个课我这样做.

using(_repository.CreateConnection())
{
    var user = _repository.GetUser();
    //do something with user
}

不幸的是,这不是一个很好的解决方案,因为人们使用这个类(甚至包括我!)经常忘记在调用方法数据库获取内容之前调用_repository.CreateConnection().

为了解决这个问题,我正在查看Mark Seemann博客文章SUT Double,他以正确的方式实现了Repository模式.不幸的是,他使Repository实现了IDisposable,这意味着我不能简单地将IoC和DI注入到类中并在之后使用它,因为在一次使用后它将被处理掉.他根据请求使用了一次,并且在请求处理完成后使用ASP.NET WebApi功能来处理它.这是我不能做的事情,因为我的类实例一直使用Repository工作.

这里最好的解决方案是什么?我应该使用某种能给我IDisposable IRepository的工厂吗?它会很容易测试吗?

解决方法

您的设计中存在一些问题点.首先,您的IRepository接口实现了多个级别的抽象.创建用户是一个比连接管理更高级别的概念.通过将这些行为放在一起,你打破了 Single Responsibility Principle,它决定了一个班级应该只有一个责任,一个改变的理由.你也违反了推动我们走向狭窄角色界面的 Interface Segregation Principle.

最重要的是,CreateConnection()和GetUser方法是时间耦合的. Temporal Coupling代码气味,你已经看到这是一个问题,因为你可以忘记对CreateConnection的调用.

除此之外,您将开始在系统中的每个存储库中看到连接的创建,并且每个业务逻辑都需要创建连接或从外部获取现有连接.从长远来看,这变得无法维持.然而,连接管理是一个贯穿各领域的问题;你不希望业务逻辑关注这种低级别的问题.

您应该首先将IRepository分成两个不同的接口:

public interface IRepository
{
    User GetUser();
}

public interface IConnectionFactory
{
    IDisposable CreateConnection();
}

您可以在更高级别管理事务,而不是让业务逻辑管理连接本身.这可能是请求,但这可能过于粗糙.您需要的是在表示层代码和业务层代码之间的某处启动事务,而不必自己复制.换句话说,您希望能够透明地应用这种横切关注点,而无需反复写入.

这是我几年前开始使用应用程序设计的众多原因之一,其中业务操作是使用消息对象定义的,其相应的业务逻辑隐藏在通用接口之后.应用这些模式后,您将拥有一个非常明确的拦截点,您可以在其中启动与其相应连接的事务,并让整个业务操作在同一事务中运行.例如,您可以使用以下通用代码,这些代码可以应用于应用程序中的每个业务逻辑:

public class TransactionCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand>
{
    private readonly ICommandHandler<TCommand> decorated;    
    public TransactionCommandHandlerDecorator(ICommandHandler<TCommand> decorated) {
        this.decorated = decorated;
    }

    public void Handle(TCommand command) {
        using (var scope = new TransactionScope()) {
            this.decorated.Handle(command);
            scope.Complete();
        }
    }   
}

代码包装TransactionScope周围的所有内容.这允许您的存储库只是打开和关闭连接;这个包装器将确保使用相同的连接.这样,您可以将IConnectionFactory抽象注入到您的存储库中,并让存储库在其方法调用结束时直接关闭连接,而在.NET下将保持打开实际连接.

猜你在找的C#相关文章