我一直试图想出一种方法来编写针对各种数据存储的通用存储库:
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)第二种方法是否有任何优势。
第一种方法是可行的,在我编写自己的映射框架时,我已经做了类似的事情,该框架针对RDBMS和 XmlWriter
/XmlReader
。您可以使用这种方法来简化单元测试,但我认为现在我们拥有优秀的OSS工具来实现这一目标。
第二种方法是我目前使用的方法 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
实际上现在普遍认为域名存储库不应该是通用的。您的存储库应该表明在保留或检索实体时可以执行的操作。
有些存储库是只读的,有些只是插入(没有更新,没有删除),有些只有特定的查找...
使用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; } }
...
}
您可以独立于数据库进行测试,但使用真正的特定存储库可以提供更多的域洞察力。
这有点晚了......但是看一下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.