仓储模式
Posted 夏风微凉
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了仓储模式相关的知识,希望对你有一定的参考价值。
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>实现。您可以在这里看到使用实体框架的示例通用存储库实现。
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)?前者更安全,但后者提供了很大的灵活性。实际上,如果你使用IQueryable路由,你可以简化你的接口,只提供一种读取数据的方法,因为从那里可以返回任意数量的项目。
这种方法的一个问题是它往往会导致业务逻辑渗透到更高的应用程序层,并在那里重复。如果返回有效客户的规则是他们没有被禁用,并且他们在去年购买了某些东西,那么最好是有一个方法ListValidCustomers()来封装这个逻辑,而不是在多个lambda表达式中指定这些条件不同的UI层对存储库的引用。实际应用中的另一个常见示例是在实体上使用由IsActive或IsDeleted属性表示的“软删除”。一旦项目被删除,99%的时间应该被排除在任何UI场景中显示,所以几乎每个请求都会包含类似
.Where(foo => foo.IsActive)
除了存在的其他过滤器之外。这可以更好地在存储库中实现,它可以是List()方法的默认行为,也可以将List()方法重命名为ListActive()。如果确实需要查看已删除/不活动的项目,则可以使用特殊的List方法来实现此目的(可能很少见)。
规范
遵循不公开IQueryable的建议的存储库通常会因许多自定义查询方法而变得臃肿。解决方案是使用规范设计模式将查询分解为它们自己的类型。规范可以包括用于过滤查询的表达式,与该表达式相关的任何参数,以及查询应该返回多少数据(即EF / EF Core中的“.Include()”)。结合存储库和规范模式可以很好地确保您在数据访问代码中遵循单一责任原则。查看如何在C#中实现通用存储库以及通用规范的示例。
以上是关于仓储模式的主要内容,如果未能解决你的问题,请参考以下文章