asp.net-mvc - 如何普遍创建从泛型类型继承的存储库?
问题描述
我目前正在尝试在我的 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){/*...*/}
}
所以问题是:
- 我正在做的事情甚至可以称为工厂方法吗?
- 如果不是,它至少是一个有效的解决方案吗?如果没有,我怎样才能以更清洁的方式实现同样的目标?
通过实现相同的目标,我的意思是实现一种以可管理、简洁的方式创建这些存储库的方法。
感谢您提前提供的所有帮助。
解决方案
首先,明确你的目标。存储库模式(至少)有 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 不需要存储库”之外,还有一些值得深思的地方:)
推荐阅读
- django - Django ORM:孩子的字段和值未继承。对象重复。(使用 Django 管理界面)
- sql - 我尝试通过以下查询找到平均工资
- python - 在python中交叉引用两个表?
- apache-kafka - 在 kafka_connect 中配置 __consumer_offset 以减少空间
- android - 我已将我的应用程序上传到 Play 商店,但有些设备没有出现
- swift - Parsing a JSON file in Swift
- c# - 主对象和嵌套对象中的 Elastic Search 2.0 搜索查询
- c++ - 如何将派生类传递给基类作为参数
- c# - JsonConvert.DeserializeObject 不保存不同的日期(例如 12/5/2020 有效,23/5/2020 无效)
- macos - macOS Catalina 上的自托管 Azure devops 构建代理不起作用