问题描述
我们正在开发一个 ASP.NET MVC 应用程序,现在正在构建存储库/服务类.我想知道创建一个所有存储库都实现的通用 IRepository 接口与每个存储库都有自己独特的接口和方法集相比是否有任何主要优势.
We are developing an ASP.NET MVC application, and are now building the repository/service classes. I'm wondering if there are any major advantages to creating a generic IRepository interface that all repositories implement, vs. each Repository having its own unique interface and set of methods.
例如:一个通用的 IRepository 接口可能看起来像(取自 这个答案):
For example: a generic IRepository interface might look like (taken from this answer):
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();
}
每个 Repository 都会实现这个接口,例如:
Each Repository would implement this interface, for example:
- CustomerRepository:IRepository
- ProductRepository:IRepository
- 等
我们在之前的项目中采用的替代方案是:
The alternate that we've followed in prior projects would be:
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);
}
在第二种情况下,表达式(LINQ 或其他)将完全包含在存储库实现中,实现服务的人只需要知道要调用哪个存储库函数.
In the second case, the expressions (LINQ or otherwise) would be entirely contained in the Repository implementation, whoever is implementing the service just needs to know which repository function to call.
我想我没有看到在服务类中编写所有表达式语法并传递到存储库的优势.这是否意味着在许多情况下容易混淆的 LINQ 代码会被复制?
I guess I don't see the advantage of writing all the expression syntax in the service class and passing to the repository. Wouldn't this mean easy-to-messup LINQ code is being duplicated in many cases?
例如,在我们旧的发票系统中,我们调用
For example, in our old invoicing system, we call
InvoiceRepository.GetSingleInvoice(DateTime invoiceDate, int accountId)
来自几个不同的服务(客户、发票、帐户等).这似乎比在多个地方编写以下内容要简洁得多:
from a few different services (Customer, Invoice, Account, etc). That seems much cleaner than writing the following in multiple places:
rep.GetSingle(x => x.AccountId = someId && x.InvoiceDate = someDate.Date);
我认为使用特定方法的唯一缺点是我们最终可能会得到许多 Get* 函数的排列,但这似乎仍然比将表达式逻辑推入服务类更可取.
The only disadvantage I see to using the specific approach is that we could end up with many permutations of Get* functions, but this still seems preferable to pushing the expression logic up into the Service classes.
我错过了什么?
推荐答案
这是一个与存储库模式本身一样古老的问题.最近 LINQ 的 IQueryable
(查询的统一表示)的引入引起了很多关于这个话题的讨论.
This is an issue as old as the Repository pattern itself. The recent introduction of LINQ's IQueryable
, a uniform representation of a query, has caused a lot of discussion about this very topic.
在努力构建通用存储库框架之后,我自己更喜欢特定的存储库.无论我尝试过什么聪明的机制,我总是遇到同样的问题:存储库是被建模域的一部分,而该域不是通用的.不是每个实体都可以删除,不是每个实体都可以添加,不是每个实体都有一个存储库.查询千差万别;存储库 API 变得与实体本身一样独特.
I prefer specific repositories myself, after having worked very hard to build a generic repository framework. No matter what clever mechanism I tried, I always ended up at the same problem: a repository is a part of the domain being modeled, and that domain is not generic. Not every entity can be deleted, not every entity can be added, not every entity has a repository. Queries vary wildly; the repository API becomes as unique as the entity itself.
我经常使用的一种模式是具有特定的存储库接口,但是实现的基类.例如,使用 LINQ to SQL,你可以这样做:
A pattern I often use is to have specific repository interfaces, but a base class for the implementations. For example, using LINQ to SQL, you could do:
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 DataContext
with your unit-of-work of choice. An example implementation might be:
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
是另一种蠕虫 - 关于该主题的意见与肚脐一样多.
Notice the public API of the repository does not allow users to be deleted. Also, exposing IQueryable
is a whole other can of worms - there are as many opinions as belly buttons on that topic.
这篇关于为每个对象创建通用存储库与特定存储库的优势?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!