问题 我们都在寻找相同的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)第二种方法是否有任何优势。


5882
2017-11-07 00:02


起源



答案:


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

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

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

// 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

5
2017-11-07 04:04





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

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

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

但是使用你提供的用于封装Linq的接口仍然很有趣 Table<T> 对象,以便您可以将其替换为内存实现以用于测试目的。

所以我建议,称之为 ITable<T>,给它与linq相同的界面 Table<T> 对象,并使用它  您的特定域名存储库(而不是代替)。

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

最简单的实现方式 ITable<T> 在记忆中是使用一个 List<T> 得到一个 IQueryable<T> 接口使用.AsQueryable()扩展方法。

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; } }
   ...
}

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


4
2018-02-06 23:27





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

关于你的问题,我看到很多人使用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.

2
2018-03-10 17:20