【问题标题】:SOLID principles, Repository pattern and EntityFramework cache in Asp Net MvcAsp Net Mvc 中的 SOLID 原理、存储库模式和 EntityFramework 缓存
【发布时间】:2017-01-21 02:26:28
【问题描述】:

我有一个使用纯色图案的视觉工作室解决方案。我有一个 IRepository(Crud 实现)、IDbFactory(由存储库使用)、IUnitOfWork。我也有服务,他们使用存储库来构建自定义查询和复杂的数据库操作。我也在使用带有 Ninject 的 IoC 模式。 在 web mvc 控制器中,我只使用服务来访问数据库。 存储库接收一个 IDbFactory,它构建一个 EntityFramework 上下文。 我有一些问题:

  • 在服务中,当我必须访问两个表以加入它们时,我应该使用两个存储库,调用它们的 GetAll() 方法。在这种情况下,两个存储库应共享相同的 EntityFramework 上下文。
  • 上一个案例表明 DbContext 应该由所有存储库共享,因此我可以将 IQueryables 加入到来自不同存储库的服务中。
  • 为了实现这个目标,我在 IoC 容器中配置了单例范围内的 DbContext。因此,每个存储库都共享相同的 DbContext。
  • 此解决方案的问题是 DbContext 有缓存。因此,当外部进程(不同于 Web 项目)更改我的数据时,DbContext 不会意识到这一点,并且 Web 项目有时不会显示真实数据。
  • 我阅读了关于在每个存储库调用中销毁 DbContext,但我不能这样做,因为每个存储库都应该使用相同的 DbContext

我的项目结构有问题吗?这是我必须在存储库层解决的问题?我该怎么办?该项目处于开发阶段,因此我可以更改数据访问架构。我喜欢在控制器中使用 IQueryables,以便用户在数据网格中过滤产生 sql 查询。

【问题讨论】:

  • CodeReview 为这类问题提供了一个更好的论坛。例如:codereview.stackexchange.com/questions/11785/…
  • 这是一个典型的例子,说明为什么构建 IRepository 通常是浪费时间。 EF 已经实现了存储库和工作单元并抽象了数据库,所以现在您已经在顶部删除了另一层抽象来实现什么?等到您开始将内容保存在一个存储库中,它会将更改保存在另一个存储库中,因为这些对象附加到相同的上下文中。如果您在网站或服务之类的地方,您可以使 DbContext 每个请求的生命周期,但 IRepository 通常比它的价值更麻烦,
  • 另一个问题是你的 DbContext 是一个单例。这意味着每个用户都在运行相同的 DbContext。破坏了 DBContext 中固有的工作单元。您希望为每个工作单元创建一个 dbcontext。

标签: .net asp.net-mvc entity-framework repository-pattern solid-principles


【解决方案1】:

嗯,就您在此处陈述的问题而言,您的项目结构没有任何问题。问题源于使用单例范围。相反,您应该将请求范围用于 Web 应用程序。这可确保您获得每个请求的新上下文,因此不同请求甚至不同客户端之间不会有重叠,这可能非常危险。

也就是说,您的项目结构设计过度。存储库/工作单元模式旨在用于低级数据访问。像实体框架一样,ORM 可以处理所有这些,事实上,实体框架已经实现了这些模式。 DbContext 是工作单元,每个 DbSet 是一个存储库。在此之上添加您自己的存储库/工作单元层是多余且不必要的。您的服务应直接使用您的实体框架上下文。即便如此,拥有多个服务也可能是不必要的,这取决于它们所做的事情。如果它们都使用相同的数据源(即实体框架上下文),特别是如果它们都使用 same 实体框架上下文,那么它们真的应该全部合二为一。

在我自己的个人项目中,我使用单个“服务”类,每个唯一上下文实例具有通用方法。通用方法让我可以处理属于该上下文的任何实体,而无需新建“服务”类的其他实例。通过确保一切都实现一个或多个接口并使用依赖注入来满足接口约束,底层数据层被完全分解。我有一个series of posts,如果您有兴趣,可以更详细地了解。

我之所以这么说,是因为您似乎过于关注模式和“最佳实践”。这些只是指南。它们就像自行车上的辅助轮。它们都基于良好代码设计的各种原则。学习并应用这些原则,不必担心在某种设计模式列表中选中所有复选框。

有趣的是,Stack Overflow 的核心代码库实际上避开了许多模式(甚至是依赖注入),因为它们专注于原始性能,而某些设计模式在应用时实际上会阻碍性能。关键是应用程序的设计方式应该基于特定应用程序的需求。设计模式应仅在与您的应用程序需求一致的情况下应用,而不仅仅是因为您认为自己应该这样做。

【讨论】:

  • 在实体框架上分层 UnitOfWork/Repository 模式的一个原因是从其他代码中抽象出实体框架。这在一些情况下帮助了我。就像当我想升级实体框架并且有一些更改破坏了工作单元存储库时,我能够进行一次更改并且其余代码没有问题。我还在 DocumentDb 中添加了一些实体,并且能够使存储库模仿现有存储库正在执行的操作,并且在检索这些项目时,其余代码看起来基本相同。
  • 不。还有其他方法可以达到相同的结果,而存储库/工作单元是最糟糕的方法。正如我在回答中所描述的,我有一个可以与 anything 一起使用的可重用类,而 Entity Framework 是完全不可见的。
  • 请解释这是“最糟糕的方式”?它已经成功地为我工作了很多年,没有任何抱怨?没有比使用工作单元更糟糕的方法了吗?你做梦也想不到会更糟?
  • 如果您有一个通用存储库,其唯一目的是将 CRUD 从应用程序的其余部分中取出并消除重复代码 (DRY),这是一种合理的方法。但是,如果您有专门的存储库,因为它们与特定实体相关而往往成为操作的“厨房水槽”,那么它就违反了 SRP。如果您愿意更改架构,您可以通过使用 CQS pattern 来解决这些“奇怪的”查询/更新,并为 CRUD 保留一组通用存储库。
  • @MikeS:这是维护的噩梦。当您抽象原始 SQL 时这是一回事,因为这更难维护,但对于抽象 ORM,尤其是已经实现这些模式的 ORM,它是多余且无用的。 Repo/UoW 不是也从未打算抽象出 ORM,将这些模式应用于此目的是对模式的混蛋。
【解决方案2】:

首先,DbContext 的生命周期应该持续到整个业务单元,在 MVC 应用程序中这是每个 HttpRequest。它提供从发送请求开始到接收用户响应的事务。

其次,您不应该在基础存储库中拥有GetAll() 函数。这是非常错误的解决方案,它会影响应用程序的性能。您应该创建如下函数:GetByQuery(Func<T, bool> whereQuery, int skip, int take) 其中 T 是您的实体类的泛型类型。或者,您可以添加一个用于排序的参数。

也许您应该考虑退出存储库模式?你的项目有多大?未来是否有可能改变数据存储?如果没有,您可以直接在服务中对 DbContext 进行操作。 Entity Framework 是存储库模式和 UnitOfWork 的一种实现。

【讨论】:

  • 如果他的 GetAll() 返回一个 IQueryable,那么他可以添加谓词、跳过获取、排序而不会影响性能,因为 EF 使用延迟执行
  • 我认为他所说的性能损失是加载表的所有记录。如果有数百万条记录,这可能会非常昂贵。然而,这一切都取决于它的使用方式。即使只允许公共访问像GetByQuery 这样的东西,仍然可以用来获取all 记录,如果您不应用任何限制。拥有GetAll 方法本身不是问题;它只是必须负责任地使用。
  • 正确。这就是我提到 IQueryable 的原因。他没有提到 GetByQuery 的返回类型,它可能是 IQueryable 而不是 List
猜你喜欢
  • 2016-03-03
  • 2015-03-22
  • 2018-11-09
  • 2016-01-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-19
  • 1970-01-01
相关资源
最近更新 更多