创建通用仓库与为每个对象创建特定仓库的优势是什么?
创建通用仓库与为每个对象创建特定仓库的优势是什么?
我们正在开发一个ASP.NET MVC应用程序,现在正在构建存储库/服务类。我想知道创建一个所有存储库都实现的通用IRepository接口与每个存储库都有自己独特的接口和方法集相比,是否有任何主要优势。
例如:一个通用的IRepository接口可能如下所示(取自此答案):
public interface IRepository : IDisposable { T[] GetAll(); T[] GetAll (Expression > filter); T GetSingle (Expression > filter); T GetSingle (Expression > filter, List >> subSelectors); void Delete (T entity); void Add (T entity); int SaveChanges(); DbTransaction BeginTransaction(); }
每个存储库都将实现这个接口,例如:
- CustomerRepository:IRepository
- ProductRepository:IRepository
- 等等。
我们之前的项目中遵循的另一种方法是:
public interface IInvoiceRepository : IDisposable { EntityCollectionGetAllInvoices(int accountId); EntityCollection GetAllInvoices(DateTime theDate); InvoiceEntity GetSingleInvoice(int id, bool doFetchRelated); InvoiceEntity GetSingleInvoice(DateTime invoiceDate, int accountId); //unique InvoiceEntity CreateInvoice(); InvoiceLineEntity CreateInvoiceLine(); void SaveChanges(InvoiceEntity); //handles inserts or updates void DeleteInvoice(InvoiceEntity); void DeleteInvoiceLine(InvoiceLineEntity); }
在第二种情况中,表达式(LINQ或其他)将完全包含在存储库实现中,实现服务的人只需要知道调用哪个存储库函数。
我想不明白的是,为什么要在服务类中编写所有表达式语法并传递给存储库。这难道不意味着容易出错的LINQ代码在许多情况下被复制吗?
例如,在我们的旧发票系统中,我们从几个不同的服务(Customer、Invoice、Account等)调用
InvoiceRepository.GetSingleInvoice(DateTime invoiceDate, int accountId)
这似乎比在多个地方编写以下代码更清晰:
rep.GetSingle(x => x.AccountId = someId && x.InvoiceDate = someDate.Date);
使用特定方法的唯一缺点我看到的是,我们可能会有很多Get*函数的排列组合,但这仍然比将表达式逻辑推到服务类中要好。
我错过了什么吗?
在创建一个通用存储库和为每个对象创建特定存储库之间存在一些分歧。一些人认为创建通用存储库可以快速开发应用程序,并在需要时进行特定化的重构。对于这种情况,他们通常创建一个具有完整CRUD操作的通用IRepository接口,并使用它进行开发和测试。然后,在需要特定查询时,他们可以通过特定存储库实现替换通用存储库的依赖关系。
然而,有人对通用存储库的表达能力提出了疑问。例如,他们不确定如何通用地表达根据ID获取对象的方法。是否应该使用泛型类型参数和ID参数的组合,还是假设ID为int类型。还有复合键的情况应如何处理。因此,有人认为通用存储库在表达某些特定需求时会显得笨拙。
另外,有人认为通用查询机制应该是ORM的责任,而不是存储库的责任。存储库应该实现项目实体的特定查询,而不是强迫使用者编写自己的查询,除非报表查询是问题域的一部分。
针对GetById()方法的具体实现,有人建议使用FindById
一些人认为创建通用存储库可以在开发早期快速启动应用程序,并在需要时逐渐特定化。然而,通用存储库在表达某些特定需求时可能会显得笨拙,因此需要根据实际情况进行重构。同时,通用查询机制应该是ORM的责任,存储库应该实现特定查询以满足项目的需求。
在创建泛型仓库与为每个对象创建特定仓库之间,有以下几个问题:
1. 代码复用:如果为每个对象创建特定仓库,会导致查询逻辑在不同领域模型中重复出现,难以实现代码的复用。
解决方法:将仓库层与业务领域分离,使其在业务领域中可以重复使用。
2. 查询逻辑的简化:使用特定仓库时,查询逻辑可能会变得复杂,特别是在涉及多个对象之间的关联查询时。
解决方法:通过拥有丰富的领域模型,可以简化查询逻辑。例如,通过富领域模型,可以使用类似"users.Where(u=>u.HasPurchasedAnything)"这样简洁的代码来搜索购买过物品的用户。
3. 抽象层的易用性:使用特定仓库可能会使抽象层变得复杂,难以使用。
解决方法:直接使用NHibernate的ISession,可以更容易地指定一些特定的查询需求,如预加载、多查询等。同时,如果需要,也可以轻松地模拟出ISession。
,对于较小的系统,直接使用NHibernate的ISession可能会更简单快捷,但在需要复用查询逻辑的情况下,将仓库层与业务领域分离并使用泛型仓库可以更好地满足代码复用和抽象层的易用性需求。
创建一个通用存储库与为每个对象创建特定存储库之间的优劣势是一个旧问题。LINQ的IQueryable的最近引入使得关于这个主题的讨论更加激烈。
我个人更喜欢使用特定存储库,之前我曾经努力构建一个通用存储库框架。无论我尝试了多么巧妙的机制,我总是遇到同样的问题:存储库是被建模的领域的一部分,而该领域不是通用的。并不是每个实体都可以被删除,也不是每个实体都可以被添加,也不是每个实体都有一个存储库。查询变化很大;存储库API变得与实体本身一样独特。
我经常使用的一种模式是具有特定存储库接口和实现的基类。例如,使用LINQ to SQL,你可以这样做:
public abstract class Repository{ private DataContext _dataContext; protected Repository(DataContext dataContext) { _dataContext = dataContext; } protected IQueryable Query { get { return _dataContext.GetTable (); } } protected void InsertOnCommit(TEntity entity) { _dataContext.GetTable ().InsertOnCommit(entity); } protected void DeleteOnCommit(TEntity entity) { _dataContext.GetTable ().DeleteOnCommit(entity); } }
将DataContext替换为您选择的工作单元。一个示例实现可能是:
public interface IUserRepository { User GetById(int id); IQueryableGetLockedOutUsers(); void Insert(User user); } public class UserRepository : Repository , IUserRepository { public UserRepository(DataContext dataContext) : base(dataContext) {} public User GetById(int id) { return Query.Where(user => user.Id == id).SingleOrDefault(); } public IQueryable GetLockedOutUsers() { return Query.Where(user => user.IsLockedOut); } public void Insert(User user) { InsertOnCommit(user); } }
注意存储库的公共API不允许删除用户。此外,公开IQueryable是另一个问题 - 对于这个主题,有许多不同的观点。
那么如何在这种情况下使用IoC/DI?(我在IoC方面是个新手)关于你的模式,我有一个完整的问题:stackoverflow.com/questions/4312388/…
“存储库是被建模的领域的一部分,而该领域不是通用的。并不是每个实体都可以被删除,也不是每个实体都可以被添加,也不是每个实体都有一个存储库”非常完美!
我知道这是一个旧答案,但我很好奇为什么在存储库类中没有包含Update方法。我很难找到一个干净的方法来做到这一点。
更新是隐式的 - 当您修改一个已跟踪的对象,然后提交DataContext时,LINQ to SQL会发出适当的命令。
老而弥坚。这篇帖子非常明智,应该被所有优秀的开发人员阅读和反复阅读。谢谢。我的实现通常基于Dapper,但原理是一样的。基础存储库,具体存储库来表示领域,选择性地使用功能。