【问题标题】:Generic Repository or Specific Repository for each entity?每个实体的通用存储库还是特定存储库?
【发布时间】:2019-01-17 04:16:24
【问题描述】:

背景

在我工作的公司,我被要求更新旧的 MVC 应用程序并为 SQL 数据库实现存储库模式。我使用 Entity Framework Database-First 创建了数据库的上下文,并获得了 23 个实体。

第一个问题

我需要为每个实体创建一个存储库还是为上下文实现一个通用存储库?我问这个是因为我在搜索互联网时发现了以下内容:

每个域一个存储库

您应该将存储库视为内存中域对象的集合。如果你正在构建一个名为 Vega 的应用程序,你不应该有一个像下面这样的存储库:

public class VegaRepository {}

相反,您应该为每个域类设置一个单独的存储库,例如 OrderRepository、ShippingRepository 和 ProductRepository。

来源:Programming with Mosh: 4 Common Mistakes with the Repository Pattern

第二个问题

通用存储库是否适用于实体框架数据库优先?这是因为我在搜索互联网时发现了以下内容:

实体框架

请注意,存储库模式仅在您拥有使用代码首先映射的 POCO 时才有用。否则你只会用实体打破抽象(= 存储库模式不是很有用)。如果你想为你生成一个基金会,你可以关注this article

来源:CodeProject: Repository pattern, done right

【问题讨论】:

    标签: c# entity-framework generics nhibernate repository-pattern


    【解决方案1】:

    首先,如果您使用完整的 ORM,如 Entity Framework 或 NHibernate,则应避免实现额外的存储库和工作单元层。 这是因为; ORM 本身公开了通用存储库和工作单元。
    对于 EF,您的 DbContext 是工作单元,DbSet 是通用存储库。在 NHibernate 的情况下,它本身就是 ISession
    在现有的相同存储库上构建新的通用存储库包装器是重复工作。为什么要重新发明轮子?

    但是,有人认为直接在调用代码中使用 ORM 存在以下问题:

    1. 由于缺乏关注点分离,它使代码稍微复杂一些。
    2. 数据访问代码合并到业务逻辑中。结果,冗余的复杂查询逻辑分布在多个地方;难以管理。
    3. 由于在调用代码中内联使用了许多 ORM 对象,因此很难对代码进行单元测试。
    4. 由于 ORM 仅公开 Generic Repository,它会导致下面提到的许多问题。

    除上述所有内容外,通常讨论的另一个问题是“如果我们决定将来更改 ORM 会怎样”。这不应该是做出决定时的关键点,因为:

    • 您很少更改 ORM,大多数情况下从不更改 - YAGNI。
    • 如果您更改 ORM,无论如何您都必须进行巨大的更改。您可以通过在 something 中封装完整的数据访问代码(不仅仅是 ORM)来减少工作量。我们将在下面讨论某事

    考虑到上面提到的四个问题,即使您使用完整的 ORM,也可能需要创建存储库 - 这取决于具体情况

    即使在这种情况下,也必须避免使用通用存储库。它被认为是一种反模式。

    为什么通用存储库是反模式的?

    1. 存储库是被建模域的一部分,并且该域不是通用的。
      • 并非每个实体都可以删除。
      • 并非每个实体都可以添加
      • 并非每个实体都有存储库。
      • 查询差异很大;存储库 API 变得与实体本身一样独特。
      • 对于GetById(),标识符类型可能不同。
      • 无法更新特定字段 (DML)。
    2. 通用查询机制是 ORM 的职责。
      • 大多数 ORM 都公开了与通用存储库非常相似的实现。
      • 存储库应使用 ORM 公开的通用查询机制来实现实体的特定查询。
    3. 无法使用复合键。
    4. 无论如何它都会泄露服务中的 DAL 逻辑。
      • 如果您接受作为参数,则谓词标准需要从服务层提供。如果这是 ORM 特定的类,它会将 ORM 泄漏到服务中。

    我建议您阅读这些(12345)文章,这些文章解释了为什么通用存储库是一种反模式。这个other 回答讨论了一般的存储库模式。

    所以,我会建议:

    • 根本不要使用存储库,直接在调用代码中使用 ORM。
    • 如果您必须使用存储库,请不要尝试使用通用存储库来实现所有内容。
      相反,可以选择创建非常简单且小型的通用存储库作为抽象基类。或者,如果 ORM 允许,您可以使用 ORM 公开的通用存储库作为基础存储库。
      根据您的需要实施具体存储库,并从通用存储库中派生所有它们。向调用代码公开具体存储库。
      通过这种方式,您可以获得通用存储库的所有优点,同时绕过它的缺点。
      尽管非常罕见,但这也有助于将来切换 ORM,因为 ORM 代码在 DAL/Repositories 中被清晰地抽象出来。请理解,切换 ORM 不是数据访问层或存储库的主要目标。

    在任何情况下,都不要将通用存储库暴露给调用代码。

    另外,不要从具体存储库返回IQueryable。这违反了存储库存在的基本目的——抽象数据访问。由于将IQueryable 暴露在存储库之外,许多数据访问决策会泄漏到调用代码中,并且存储库会失去对它的控制。

    我需要为每个实体创建一个存储库还是为上下文实现一个通用存储库

    如上所述,为每个实体创建存储库是更好的方法。请注意,存储库理想情况下应该返回域模型而不是实体。但这是不同的讨论主题。

    通用存储库是否适用于 EF Database First?

    如上所述,EF 本身公开了通用存储库。在它上面再建一层是没有用的。你的图片在说同样的事情。

    【讨论】:

    • "另外,不要从具体的存储库返回 IQueryable" 将 DbSet 直接用作存储库时如何避免这种情况? “您很少更改 ORM,大多数情况下从不更改”从 .Net 数据集更改为 Linq2Sql,然后更改为 EntityFramework,现在更改为 EFCore 是我的经验中的真实场景(当 ORM 没有到处泄漏时,它的工作量就更少了)+1 无论如何这个完整的答案
    • 在将 DbSet 直接用作存储库时如何避免这种情况? 不;在那种情况下你不能。我的陈述与我们编写的通用存储库有关。
    • 这就是我的意思,从你的回答中强调“DbSet 是通用存储库”,它“违反了存储库的基本目的”(我只是在开玩笑,这个主题主要是基于意见的,而你暴露得很好)
    猜你喜欢
    • 2012-03-10
    • 1970-01-01
    • 2012-06-20
    • 2020-03-31
    • 1970-01-01
    • 2019-06-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多