Repository模式自2004年首次做为领域驱动设计的一部分引入以来,已经得到了至关多的知名度。本质上,它提供了数据的抽象,以便您的应用程序可使用具备接口近似的简单抽象一个集合的。从这个集合中添加,删除,更新和选择项目是经过一系列直接的方法完成的,无需处理链接,命令,光标或读取器等数据库问题。使用这种模式能够帮助实现松耦合,而且能够保持领域对象的持久性无知。虽然这种模式很是流行(或者也许正由于如此),但它也常常被误解和误用。有不少不一样的方式来实现Repository模式。让咱们考虑其中的一些,以及它们的优势和缺点。数据库
最简单的方法,特别是对于现有系统,是为每一个须要存储或从持久层检索的业务对象建立一个新的Repository实现。此外,您只应实现您在应用程序中调用的特定方法。避免建立必须为全部存储库实施的“标准”存储库类,基类或默认接口的陷阱。是的,若是你须要一个Update或Delete方法,你应该努力使它的接口保持一致(Delete带一个ID,仍是带对象自己?),可是不要在你的LookupTableRepository上实现一个Delete方法你只会打电话给List()。这种方法最大的好处是 YAGNI - 你不会浪费任什么时候间来实现永远不会被调用的方法。设计模式
另外一种方法是继续为您的Repository建立一个简单的通用界面。你能够约束它的工做类型是什么类型,或者实现一个特定的接口(例如确保它有一个Id属性,以下面使用基类所作的那样)。通用C#存储库接口的一个例子多是:安全
public interface IRepository<T> where T : EntityBase { T GetById(int id); IEnumerable<T> List(); IEnumerable<T> List(Expression<Func<T, bool>> predicate); void Add(T entity); void Delete(T entity); void Edit(T entity); } public abstract class EntityBase { public int Id { get; protected set; } }
这种方法的优势是它能够确保你有一个通用的界面来处理你的任何对象。您还可使用通用存储库实现(如下)简化实现。请注意,接受谓词消除了返回IQueryable的必要性,由于能够将任何过滤器详细信息传递到存储库中。尽管如此,这仍然可能致使数据访问细节泄漏到调用代码中。若是遇到问题,请考虑使用规范模式(以下所述)来缓解此问题。框架
假设您建立了一个通用储存库接口,您也能够通常地实现该接口。完成此操做后,您能够轻松建立任何给定类型的存储库,而无需编写任何新代码,而声明依赖关系的类能够简单地指定IRepository <Item>做为类型,而且您的IoC容器很容易与之匹配与一个存储库<Item>实现。您能够在这里看到使用实体框架的示例通用存储库实现。spa
public class Repository<T> : IRepository<T> where T : EntityBase { private readonly ApplicationDbContext _dbContext; public Repository(ApplicationDbContext dbContext) { _dbContext = dbContext; } public virtual T GetById(int id) { return _dbContext.Set<T>().Find(id); } public virtual IEnumerable<T> List() { return _dbContext.Set<T>().AsEnumerable(); } public virtual IEnumerable<T> List(System.Linq.Expressions.Expression<Func<T, bool>> predicate) { return _dbContext.Set<T>() .Where(predicate) .AsEnumerable(); } public void Insert(T entity) { _dbContext.Set<T>().Add(entity); _dbContext.SaveChanges(); } public void Update(T entity) { _dbContext.Entry(entity).State = EntityState.Modified; _dbContext.SaveChanges(); } public void Delete(T entity) { _dbContext.Set<T>().Remove(entity); _dbContext.SaveChanges(); } }
请注意,在此实现中,全部操做都在执行时保存; 没有适用的工做单元。工做单元行为能够经过多种方式添加到此实现中,其中最简单的方法是将明确的Save()方法添加到IRepository <T>方法,并仅调用基础的SaveChanges()方法从这个方法。设计
存储库的另外一个常见问题与他们返回的内容有关。他们应该返回数据,仍是应该返回能够在执行前进一步细化的查询(IQueryable)?前者更安全,但后者提供了很大的灵活性。实际上,若是你使用IQueryable路由,你能够简化你的接口,只提供一种读取数据的方法,由于从那里能够返回任意数量的项目。code
这种方法的一个问题是它每每会致使业务逻辑渗透到更高的应用程序层,并在那里重复。若是返回有效客户的规则是他们没有被禁用,而且他们在去年购买了某些东西,那么最好是有一个方法ListValidCustomers()来封装这个逻辑,而不是在多个lambda表达式中指定这些条件不一样的UI层对存储库的引用。实际应用中的另外一个常见示例是在实体上使用由IsActive或IsDeleted属性表示的“软删除”。一旦项目被删除,99%的时间应该被排除在任何UI场景中显示,因此几乎每一个请求都会包含相似对象
.Where(foo => foo.IsActive)blog
除了存在的其余过滤器以外。这能够更好地在存储库中实现,它能够是List()方法的默认行为,也能够将List()方法重命名为ListActive()。若是确实须要查看已删除/不活动的项目,则可使用特殊的List方法来实现此目的(可能不多见)。接口
规范
遵循不公开IQueryable的建议的存储库一般会因许多自定义查询方法而变得臃肿。解决方案是使用规范设计模式将查询分解为它们本身的类型。规范能够包括用于过滤查询的表达式,与该表达式相关的任何参数,以及查询应该返回多少数据(即EF / EF Core中的“.Include()”)。结合存储库和规范模式能够很好地确保您在数据访问代码中遵循单一责任原则。查看如何在C#中实现通用存储库以及通用规范的示例。