创build一个通用的存储库与每个对象的特定存储库的优势?
我们正在开发一个ASP.NET MVC应用程序,现在正在构build资源库/服务类。 我想知道是否有任何创build一个通用的IRepository接口,所有的存储库实现,与每个存储库有其自己独特的接口和方法集有什么大的优势。
例如:一个通用的IRepository接口可能看起来像(取自这个答案 ):
public interface IRepository : IDisposable { T[] GetAll<T>(); T[] GetAll<T>(Expression<Func<T, bool>> filter); T GetSingle<T>(Expression<Func<T, bool>> filter); T GetSingle<T>(Expression<Func<T, bool>> filter, List<Expression<Func<T, object>>> subSelectors); void Delete<T>(T entity); void Add<T>(T entity); int SaveChanges(); DbTransaction BeginTransaction(); }
每个存储库将实现这个接口,例如:
- CustomerRepository:IRepository
- ProductRepository:IRepository
- 等等
我们在之前的项目中采用的替代scheme是:
public interface IInvoiceRepository : IDisposable { EntityCollection<InvoiceEntity> GetAllInvoices(int accountId); EntityCollection<InvoiceEntity> 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); }
在第二种情况下,expression式(LINQ或其他)将完全包含在Repository实现中,无论谁在实现该服务,只需要知道要调用哪个存储库函数。
我想我没有看到写服务类中的所有expression式语法,并传递到存储库的优势。 难道这不意味着很容易misup LINQ代码被重复在很多情况下?
例如,在我们以前的发票系统中,我们称之为
InvoiceRepository.GetSingleInvoice(DateTime invoiceDate, int accountId)
从一些不同的服务(客户,发票,帐户等)。 这似乎比在多个地方编写以下内容更清洁:
rep.GetSingle(x => x.AccountId = someId && x.InvoiceDate = someDate.Date);
我看到使用特定方法的唯一缺点是,我们可能会得到许多Get *函数的排列,但这似乎更适合将expression式逻辑推入Service类。
我错过了什么?
这是一个和Repository模式本身一样古老的问题。 最近引入了LINQ的IQueryable
,一个查询的统一表示,引起了很多关于这个话题的讨论。
在非常努力地构build通用的存储库框架之后,我自己更喜欢特定的存储库。 无论我尝试什么聪明的机制,我总是会遇到同样的问题:一个存储库是所build模域的一部分,而且这个域不是通用的。 不是每个实体都可以被删除,不是每个实体都可以被添加,并不是每个实体都有一个存储库。 查询变化很大; 存储库API变得与实体本身一样唯一。
我经常使用的模式是具有特定的存储库接口,但实现的基类。 例如,使用LINQ to SQL,你可以这样做:
public abstract class Repository<TEntity> { private DataContext _dataContext; protected Repository(DataContext dataContext) { _dataContext = dataContext; } protected IQueryable<TEntity> Query { get { return _dataContext.GetTable<TEntity>(); } } protected void InsertOnCommit(TEntity entity) { _dataContext.GetTable<TEntity>().InsertOnCommit(entity); } protected void DeleteOnCommit(TEntity entity) { _dataContext.GetTable<TEntity>().DeleteOnCommit(entity); } }
将DataContext
replace为您所select的工作单元。 一个示例实现可能是:
public interface IUserRepository { User GetById(int id); IQueryable<User> GetLockedOutUsers(); void Insert(User user); } public class UserRepository : Repository<User>, IUserRepository { public UserRepository(DataContext dataContext) : base(dataContext) {} public User GetById(int id) { return Query.Where(user => user.Id == id).SingleOrDefault(); } public IQueryable<User> GetLockedOutUsers() { return Query.Where(user => user.IsLockedOut); } public void Insert(User user) { InsertOnCommit(user); } }
注意存储库的公共API不允许删除用户。 另外,暴露IQueryable
是另外一种蠕虫 – 在这个话题上有许多意见。
我实际上与布莱恩的职位稍有不同。 我认为他是对的,最终一切都是非常独特的,等等。 但是与此同时,大多数是在devise时出现的,我发现在开发我的模型的时候获得一个通用的仓库并使用它,我可以很快得到一个应用程序,然后重新find更大的特定性,因为我find需要这样做。
所以,在这种情况下,我经常创build一个具有完整CRUD堆栈的通用IRepository,这使我能够快速地使用API,让人们在用户界面上进行游戏,并行地进行集成和用户验收testing。 然后,因为我发现我需要在回购等具体查询,我开始取代这种依赖w /特定的一个如果需要,并从那里去。 一个潜在的impl。 很容易创build和使用(并可能挂钩到内存中的数据库或静态对象或模拟对象或其他)。
也就是说,我最近开始做的是打破行为。 因此,如果您为IDataFetcher,IDataUpdater,IDataInserter和IDataDeleter(例如)创build接口,则可以混合搭配以通过接口定义您的需求,然后实现部署或部分处理它们,并且可以仍然注入了我在构build应用程序时使用的全部实现。
保罗
我更喜欢具有可重写方法签名的通用存储库(或通用存储库列表来指定确切的行为)的特定存储库。
Greg Young在这个话题上有一个很棒的职位。 http://codebetter.com/gregyoung/2009/01/16/ddd-the-generic-repository/
拥有一个由特定存储库包装的通用存储库。 这样你就可以控制公共接口,但仍然具有来自具有通用存储库的代码重用的优点。
公共类UserRepository:存储库,IUserRepository
不应该注入IUserRepository以避免暴露接口。 正如人们所说,你可能不需要完整的CRUD堆栈等。