我们都在寻找相同的IRepository吗?

时间:2008-11-07 00:02:57

标签: domain-driven-design design-patterns irepository

我一直试图想出一种编写适用于各种数据存储的通用存储库的方法:

public interface IRepository
{
    IQueryable<T> GetAll<T>();
    void Save<T>(T item);
    void Delete<T>(T item);
}
public class MemoryRepository : IRepository {...}
public class SqlRepository : IRepository {...}

我想在每个中使用相同的POCO域类。我也在考虑一种类似的方法,每个域类都有自己的存储库:

public interface IRepository<T>
{
    IQueryable<T> GetAll();
    void Save(T item);
    void Delete(T item);
}
public class MemoryCustomerRepository : IRepository {...}
public class SqlCustomerRepository : IRepository {...}

我的问题:1)第一种方法是否可行? 2)第二种方法是否有任何优势。

3 个答案:

答案 0 :(得分:5)

  1. 第一种方法是可行的,我在编写自己的映射框架时做了类似的事情,该框架针对RDBMS和XmlWriter / XmlReader。您可以使用这种方法来简化单元测试,但我认为现在我们拥有优秀的OSS工具来实现这一目标。

  2. 第二种方法是我目前使用的IBATIS.NET mappers。每个映射器都有一个接口,每个映射器[都]可以提供基本的CRUD操作。优点是域类的每个映射器还具有由接口表示并在具体映射器中定义的特定函数(例如SelectByLastNameDeleteFromParent)。因此,我不需要像你建议的那样实现单独的存储库 - 我们的具体映射器以数据库为目标。要执行单元测试,我使用StructureMapMoq来创建与Memory*Repository一样运行的内存存储库。它实现和管理的类较少,而且对于一种非常可测试的方法而言整体工作较少。对于跨单元测试共享的数据,我对每个具有WithXXX方法和AsSomeProfile方法的域类使用构建器模式(AsSomeProfile只返回具有预配置测试数据的构建器实例)。

  3. 这是我在单元测试中通常最终得到的一个例子:

    // Moq mocking the concrete PersonMapper through the IPersonMapper interface
    var personMock = new Mock<IPersonMapper>(MockBehavior.Strict);
    personMock.Expect(pm => pm.Select(It.IsAny<int>())).Returns(
        new PersonBuilder().AsMike().Build()
    );
    
    // StructureMap's ObjectFactory
    ObjectFactory.Inject(personMock.Object);
    
    // now anywhere in my actual code where an IPersonMapper instance is requested from
    // ObjectFactory, Moq will satisfy the requirement and return a Person instance
    // set with the PersonBuilder's Mike profile unit test data
    

答案 1 :(得分:4)

实际上现在普遍认为域名存储库不应该是通用的。您的存储库应该表明在保存或检索实体时可以执行的操作。

某些存储库是只读的,有些只是插入(没有更新,没有删除),有些只有特定的查找......

使用GetAll返回IQueryable,您的查询逻辑将泄漏到您的代码中,可能泄漏到应用程序层。

但是使用你提供的那种接口来封装Linq Table<T>对象仍然很有趣,这样你就可以用内存实现替换它以进行测试。

所以我建议,将其命名为ITable<T>,为其提供与linq Table<T>对象相同的界面,并在其中使用 特定的域名存储库(而不是)。

然后,您可以使用内存ITable<T>实现在内存中使用特定的存储库。

在内存中实现ITable<T>的最简单方法是使用List<T>并使用.AsQueryable()扩展方法获取IQueryable<T>接口。

public class InMemoryTable<T> : ITable<T>
{
    private List<T> list;
    private IQueryable<T> queryable;

   public InMemoryTable<T>(List<T> list)
   { 
      this.list = list;
      this.queryable = list.AsQueryable();
   }

   public void Add(T entity) { list.Add(entity); }
   public void Remove(T entity) { list.Remove(entity); }

   public IEnumerator<T> GetEnumerator() { return list.GetEnumerator(); }

   public Type ElementType { get { return queryable.ElementType; } }
   public IQueryProvider Provider {     get { return queryable.Provider; } }
   ...
}

您可以独立于数据库进行测试,但使用真正的特定存储库可以提供更多的域洞察力。

答案 2 :(得分:2)

这有点晚了......但是看看CommonLibrary.NET上关于codeplex的IRepository实现。它有一个非常好的功能集。

关于你的问题,我看到很多人使用GetAllProducts(),GetAllEmployees()等方法 在他们的存储库实现。这是多余的,不允许您的存储库是通用的。 您只需要GetAll()或All()。上面提供的解决方案确实解决了命名问题。

这取自在线的CommonLibrary.NET文档:

0.9.4 Beta 2具有强大的Repository实现。

* Supports all CRUD methods ( Create, Retrieve, Update, Delete )
* Supports aggregate methods Min, Max, Sum, Avg, Count
* Supports Find methods using ICriteria<T>
* Supports Distinct, and GroupBy
* Supports interface IRepository<T> so you can use an In-Memory table for unit-testing
* Supports versioning of your entities
* Supports paging, eg. Get(page, pageSize)
* Supports audit fields ( CreateUser, CreatedDate, UpdateDate etc )
* Supports the use of Mapper<T> so you can map any table record to some entity
* Supports creating entities only if it isn't there already, by checking for field values.