11

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

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)第二种方法有什么优势吗?

4

3 回答 3

5
  1. 第一种方法是可行的,我过去在编写自己的针对 RDBMS 和XmlWriter/的映射框架时做过类似的事情XmlReader。您可以使用这种方法来简化单元测试,尽管我认为现在我们拥有出色的 OSS 工具可以做到这一点。

  2. 第二种方法是我现在使用的IBATIS.NET 映射器。每个映射器都有一个接口,每个映射器 [可以] 提供您的基本 CRUD 操作。优点是域类的每个映射器还具有由接口表示并在具体映射器中定义的特定功能(例如SelectByLastName或)。DeleteFromParent因此,我不需要像您建议的那样实现单独的存储库 - 我们的具体映射器以数据库为目标。为了执行单元测试,我使用StructureMapMoq创建内存存储库,作为您的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
于 2008-11-07T04:04:26.467 回答
4

实际上,现在人们普遍认为域存储库不应该是通用的。您的存储库应该表达您在持久化或检索实体时可以执行的操作。

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

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

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

所以我建议,调用它,给它与 linq对象ITable<T>相同的接口,并你的特定域存储库中使用它(而不是代替)。Table<T>

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

在内存中实现的最简单方法ITable<T>是使用 aList<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; } }
   ...
}

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

于 2009-02-06T23:27:43.933 回答
2

This is a bit late... but take a look at the IRepository implementation at CommonLibrary.NET on codeplex. It's got a pretty good feature set.

Regarding your problem, I see a lot of people using methods like GetAllProducts(), GetAllEmployees() in their repository implementation. This is redundant and doesn't allow your repository to be generic. All you need is GetAll() or All(). The solution provided above does solve the naming problem though.

This is taken from CommonLibrary.NET documentation online:

0.9.4 Beta 2 has a powerful Repository implementation.

* 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.
于 2010-03-10T17:20:05.723 回答