4

我看过很多关于 UnitOfWork 和 Repository 的帖子(和辩论!)。我喜欢的存储库模式之一是类型化的通用存储库模式,但我担心这会导致一些干净的代码和可测试性问题。采用以下存储库接口和泛型类:

public interface IDataEntityRepository<T> : IDisposable where T : IDataEntity
{
   // CRUD
   int Create(T createObject);
   // etc.
}


public class DataEntityRepository<T> : IDataEntityRepository<T> where T : class, IDataEntity
{
   private IDbContext Context { get; set; }

   public DataEntityRepository (IDbContext context)
   {
     Context = context;
   }

   private IDbSet<T> DbSet { get { return Context.Set<T>(); } }   

   public int Create(T CreateObject)
   {
      DbSet.Add(createObject);
   }

   // etc.

}

// where

public interface IDbContext
{
   IDbSet<T> Set<T>() where T : class;
   DbEntityEntry<T> Entry<T>(T readObject) where T : class;

   int SaveChanges();
   void Dispose();
}

所以基本上我在每个模式中使用 Context 属性来访问底层上下文。我现在的问题是:当我创建我的工作单元时,它实际上是我需要存储库知道的上下文的包装器。因此,如果我有一个声明以下内容的工作单元:

public UserUnitOfWork(
    IDataEntityRepository<User> userRepository,
    IDataEntityRepository<Role> roleRepository)
{
    _userRepository = userRepository;
    _roleRepository = roleRepository;
}

private readonly IDataEntityRepository<User> _userRepository;

public IDataEntityRepository<User> UserRepository
{
    get { return _userRepository; }
}

private readonly IDataEntityRepository<Role> _roleRepository;

public IDataEntityRepository<Role> RoleRepository
{
    get { return _roleRepository; }
}

我有一个问题,即我传入的两个存储库都需要使用它们被传递到的工作单元进行实例化。显然,我可以在构造函数中实例化存储库并传入“this”,但这会将我的工作单元与存储库的特定具体实例紧密耦合,并使单元测试变得更加困难。我很想知道是否还有其他人沿着这条路走并撞到了同一堵墙。这两种模式对我来说都是新的,所以我很可能会做一些根本错误的事情。任何想法将不胜感激!

更新(回复@MikeSW)

嗨,迈克,非常感谢您的意见。我正在使用 EF Code First,但我想抽象某些元素,以便在需要时切换到不同的数据源或 ORM,因为我(正在尝试!)将自己推向 TDD 路线并使用 Mocking 和 IOC。我想我已经意识到某些元素不能在纯粹意义上进行单元测试但可以进行集成测试的艰难方式!我想就存储库与业务对象或视图模型等一起工作提出您的观点。也许我误解了,但如果我有我认为的核心业务对象 (POCO),然后我想使用 ORM,例如 EF 代码首先环绕这些实体以创建数据库,然后与之交互(并且,有可能,我可以在 ViewModel 中重用这些实体),我希望存储库能够在一组 CRUD 操作的上下文中直接处理这些实体。实体对持久层一无所知,任何 ViewModel 也不知道。我的工作单元只是实例化并保存允许执行事务提交的所需存储库(跨多个存储库但相同的上下文/会话)。我在我的解决方案中所做的是从 UnitOfWork 构造函数中删除 IDataEntityRepository ...等的注入,因为这是一个具体的类,它必须知道它应该创建的一种且只有一种类型的 IDataEntityRepository(在本例中为 DataEntityRepository , 这确实应该更好地命名为 EFDataEntityRepository)。我无法对其本身进行单元测试,因为整个单元逻辑将是建立具有某个数据库的上下文(本身)的存储库。它只需要一个集成测试。希望这是有道理的?!

4

3 回答 3

3

为避免依赖工作单元中的每个存储库,您可以使用基于此合同的提供程序:

public interface IRepositoryProvider
{
    DbContext DbContext { get; set; }
    IRepository<T> GetRepositoryForEntityType<T>() where T : class;
    T GetRepository<T>(Func<DbContext, object> factory = null) where T : class;
    void SetRepository<T>(T repository);
}

然后你可以将它注入到你的 UoW 中,如下所示:

public class UserUnitOfWork: IUserUnitOfWork
{
    public UserUnitOfWork(IRepositoryProvider repositoryProvider)
    {
        RepositoryProvider = repositoryProvider;
    }

    protected IDataEntityRepository<T> GetRepo<T>() where T : class
    {
        return RepositoryProvider.GetRepositoryForEntityType<T>();
    }

    public IDataEntityRepository<User> Users { get { return GetRepo<User>(); } }        
    public IDataEntityRepository<Role> Roles { get { return GetRepo<Role>(); } }        
...
于 2013-01-20T13:01:27.017 回答
3

为我的反应迟缓道歉 - 我一直在尝试各种方法来解决这个问题。我已经标记了上面的答案,因为我同意所做的评论。

这是有多个答案的问题之一,它在很大程度上取决于整体方法。虽然我同意 EF 有效地提供了现成的工作单元模式,但我决定创建自己的工作单元和存储库层是为了能够控制对数据库实体的访问。

我挣扎的地方是需要能够将存储库注入到工作单元中。但我意识到,在 EF 的情况下,我的工作单元实际上是一个使用 Commit (SaveChanges) 方法围绕多个存储库的瘦包装器。它不负责执行 FindCustomer 等特定操作。

所以我决定一个工作单元可以与其特定类型的 DataRepository 模式紧密耦合。为了确保我有一个可测试的模式,我引入了一个服务层,它提供了用于执行特定操作(如 CreateCustomer、FindCustomers 等)的外观。这些服务接受了一个 IUnitOfWork 构造函数参数,该参数提供了对存储库(作为接口)的访问以及提交方法。

然后,我能够为测试目的创建工作单元和/或存储库的伪造品。这只是让我决定哪些可以用假货进行单元测试,哪些需要用具体实例进行集成测试。

这也让我有机会控制对数据库执行哪些操作以及如何执行这些操作。

我敢肯定有很多方法可以给这只特殊的猫换皮,但是提供一个可测试的干净界面的目标已经通过这种方法实现了。

感谢 g1ga 和 Mike 的投入。

于 2013-03-01T11:03:16.197 回答
2

使用实体框架 (EF)(我假设您正在使用)时,您已经拥有一个通用存储库 IDbSet。仅仅为了调用 EF 方法而在顶部添加另一层是没有用的。

此外,存储库与应用程序对象(通常是业务对象,但它们可以是视图模型或对象状态)一起使用。如果您只是使用数据库实体,那么您有点违背了存储库模式的目的(将业务对象与数据库隔离)。原始模式只处理业务对象,但它也是业务层之外的有用模式。

关键是 EF 实体是 Persistence 对象,并且与您的业务对象没有(或应该有)任何关系。您想使用存储库模式将业务对象“转换”为持久性对象,反之亦然。

有时可能会发生应用程序对象(如视图模型)与持久性实体相同的情况(在这种情况下,您可以直接使用 EF 对象),但这是巧合。

关于工作单元(UoW),假设这很棘手。就个人而言,我更喜欢使用 DDD(域驱动设计)方法,并认为发送到存储库的任何业务对象 (BO) 都是 UoW,因此它将被包装在事务中。

如果我需要更新多个 BO,我将使用消息驱动架构向相关 BO 发送命令。当然,这更复杂,需要对最终一致性的概念感到轻松,但我并不依赖于特定的 RDBMS。

如果您知道您将使用特定的 RDBMS 并且永远不会更改,您可以启动一个事务并将关联的连接传递给每个存储库,最后提交一个提交(这将是 UoW)。如果您在网络设置中,则更容易,在请求开始时启动事务,在请求结束时提交(您可以使用 ASp.Net Mvc 的 ActionFilter)。

然而,该解决方案与一个 RDBMS 相关联,因此它不适用于 NoSql 或任何不支持事务的存储。对于这些情况,消息驱动的方式是最好的。

于 2013-01-08T14:23:36.183 回答