首页 > 解决方案 > 如何普遍创建从泛型类型继承的存储库?

问题描述

我目前正在尝试在我的 DbContext 之上实现存储库模式。问题是,我最终会遇到必须将多个存储库注入UnitOfWork构造函数的情况,如下所示:

public class UnitOfWork
{
    private DbContext _context;
    ICustomerRepository Customers { get; private set; }
    IEmployeeRepository Employees { get; private set; } 
    public UnitOfWork(DbContext context, ICustomerRepository cust, IEmployeeRepository emp)
    {
        _context = context;
        Customers = cust;
        Employees = emp;
    }    
}

但是,由于它们都必须共享相同的 DbContext,因此我看不到将它们作为选项注入。

这就是我考虑创建一个RepositoryFactory类的原因,但是由于所有存储库都继承自一个通用接口,我发现不可能创建一个Create()方法,因为毕竟它们没有真正的共同祖先,这将是一个有效的返回类型。

为了让您更深入地了解,代码如下所示

public interface IRepository<TEntity> where TEntity:class
{
    TEntity Get(int id);
    IEnumerable<TEntity> GetAll();
}

public interface ICustomerRepository : IRepository<Customer>
{
    IEnumerable<Customer> GetSeniorCustomers();
}

public class CustomerRepository : ICustomerRepository
{
    private readonly DbContext _context;
    public CustomerRepository(DbContext context) : base(context)
    {
        _context = context;
    }
// ... implementation of ICustomerRepo here
}

现在,这就是目前的情况:

我想做的是:

public UnitOfWork(DbContext context, RepositoryFactory fac)
{
     _context = context;
     Customers = fac.Create(context, RepoType.Customer);
     Employees = fac.Create(context, RepoType.Employee);
}  

我知道它并没有真正给我任何额外的灵活性,但在我看来它确实使代码不那么笨拙。

但是,正如我之前提到的,我想不出 Create() 方法的有效返回类型。

所以,我想出了在 RepositoryFactory 类中创建多个方法的想法,而不是单个参数化的方法,如下所示:

public class RepositoryFactory
{
    public ICustomerRepository CreateCustomerRepo(DbContext context){/*...*/}
    public IEmployeeRepository CreateEmployeeRepo(DbContext context){/*...*/} 
}

所以问题是:

  1. 我正在做的事情甚至可以称为工厂方法吗?
  2. 如果不是,它至少是一个有效的解决方案吗?如果没有,我怎样才能以更清洁的方式实现同​​样的目标?

通过实现相同的目标,我的意思是实现一种以可管理、简洁的方式创建这些存储库的方法。

感谢您提前提供的所有帮助。

标签: asp.net-mvcentity-frameworkrepository-patternfactory

解决方案


首先,明确你的目标。存储库模式(至少)有 3 个存在的关键原因:

1)抽象出数据层。就 EF 而言,如果这是您的目标,那么存储库模式将不再有益。试图从 Entity Framework 中抽象应用程序的麻烦远远超过它的价值。您最终会得到一个/两个残缺的 DAL,其中 EF 可以提供的强大功能不可用或效率低下/速度慢,或者是一堆非常复杂的存储库方法,其参数是表达式和​​其他讨厌的东西。试图将您的应用程序从 EF 中抽象出来(例如,如果您可能想更改为另一个 ORM)是没有意义的。接受 EF,就像接受在 .Net 中编写应用程序的事实一样。要将 EF 抽象到可以替换的程度,您最好不要使用它,因为您不会看到 EF 实际可以提供的任何好处。

2)使业务逻辑更容易测试。IMO 对于具有实体框架的存储库,这仍然是一个有效的论据。是的,EF DbContexts可以被嘲笑,但它们仍然是一团糟。模拟存储库并不比任何其他依赖项更难。

3) 作为域看门人。DDD 等模式旨在锁定针对域对象和服务中的数据的操作。当使用 EF 来帮助包含负责操作域的方法时,存储库模式可以提供帮助。对于纯 DDD,我不推荐它们,尽管我也不推荐使用实体作为 DDD 域对象。我确实使用存储库模式来管理 CRUD 的 CR 和 D. 方面,并依靠视图模型来封装围绕 U 的域逻辑。

我发现在服务点 2 和 3 中最常用的存储库模式是取消通用存储库的非常常见的概念,而是将存储库更符合您在 MVC 中处理控制器的方式。除了 View 和 Model 之间,Model 和 Data 之间。存储库是一个服务于控制器(在 MVC 中)的类,因为它负责创建、读取(在核心级别)和删除实体。这种模式与工作单元结合使用非常好。(https://github.com/mehdime/DbContextScope是我采用的实现。)

在创建实体时,它负责确保提供所有必需的(不可为空的)值和引用,返回与 DbContext 关联的实体,准备就绪。实际上是一个实体工厂。您可能会争论关注点分离,尽管鉴于存储库已经可以访问 DbContext 以检索相关实体,这几乎是完成这项工作的最佳场所。

在阅读实体中,它通过提供IQueryable<TEntity>强制执行核心级别规则(例如.Where(x => x.IsActive)软删除场景)或基于可以揭示当前用户的依赖关系的身份验证/授权/租赁过滤器来提供对进一步查询实体的基本引用。通过公开IQueryable,您可以保持存储库实现简单,并让消费者(控制器)控制数据的使用方式。这可以利用延迟执行来:

  • 仅选择视图模型所需的数据。
  • 执行计数和存在检查。( .Any())
  • 针对特定用例自定义整个实体结构的过滤逻辑。
  • 执行分页。
  • 根据需要获取尽可能多的(.ToList()、.Take())或尽可能少的(.SingleOrDefault()、.FirstOrDefault())数据。

读取方法非常容易模拟,并使存储库实现占用空间非常小。消费者需要意识到他们正在处理实体,并处理 EF 及其代理的细微差别,但消费者是工作单元(DbContext 的生命周期)的守护者,因此将这一事实隐藏起来相当没有实际意义. 将复杂的查询表达式作为参数传递到存储库方法中,消费者同样有责任了解 EF 的细微差别。调用提供给通用存储库以进入 Where 子句的私有方法的表达式将同样快速地破坏事情。沿着这条路线走上面的第 1 点,不要从您的应用程序中抽象出 EF。

在删除实体中,它确保实体及其关联得到适当管理,无论是硬删除还是软删除。

我避免使用通用存储库,因为就像控制器(和视图)将处理任意数量的相关域视图模型一样,这意味着它们将需要处理许多相关的数据实体。针对任何一个实体的行动总是与针对其他附属机构的行动相关联。对于通用存储库,分离意味着 a) 越来越多的依赖项和 b) 做琐碎废话的通用方法和大量自定义代码来处理有意义的东西,或者复杂的代码尝试以通用(基本)方式促进它. 通过每个控制器拥有一个存储库,也许还有一些用于公共实体的真正通用的共享存储库。(例如查找)我的存储库被明确设计为服务于应用程序的一个区域,并且只有一个改变的理由。当然,可能有 2 个或更多屏幕需要来自存储库的相同行为,但随着应用程序或服务的这些方面成熟,它们的存储库可以根据需要成熟/优化而不会产生副作用。SRP 和 KISS 轻松胜过 DNRY。

泛型类通常有其用途,但几乎在我看到开发人员编写它们的任何情况下,我都认为这是过早的优化。从非泛型实现开始,然后随着产品的成熟,将泛型优化代码中,而不是尝试围绕它们设计架构。结果几乎总是意识到您需要对它们进行反优化或“聪明地”解决已发现的模式阻碍开发的限制。

无论如何,除了“EF 不需要存储库”之外,还有一些值得深思的地方:)


推荐阅读