【问题标题】:How can I decouple the EF POCO from the generic IRepository in this pattern?在这种模式下,如何将 EF POCO 与通用 IRepository 分离?
【发布时间】:2012-10-29 17:04:27
【问题描述】:

我目前有一个 Repository/UnitOfWork 模式。但是,有一个我无法弄清楚如何摆脱的硬耦合。


这是我的模式的概述:

业务逻辑层

  • I 存储库
    • 用作类型约束
  • IRepository
    • 实现 IRepository
    • 一般 CRUD 方法
  • IEmployeeRepository
    • 实现 IRepository
    • 一些员工特定的方法
  • IUnitOfWork
    • 存储库的获取器
    • 保存方法
  • IEntityWithId
    • 强制(和公开)DTO 和 EF POCO 具有称为 ID 的 Int32 字段的接口
    • 用作类型约束
  • EmployeeDTO(在实现的 EmployeeRepository 中使用 AutoMapper 映射)
    • 在核心项目和(即将到来的)测试项目中使用的 DTO 实体

数据层(用 Ninject 注入)

  • 工作单元
    • 基于Entity Framework的IUnitOfWork实现
  • 员工存储库
    • IEmployeeRepository 的实现
  • 员工
    • EF POCO

核心

  • 员工控制器
    • 带参数的构造函数EmployeesController(IUnitOfWork unitOfWork)
    • IUnitOfWork 使用 Ninject 模块作为 UnitOfWork 注入(来自数据层)

这些是我的通用 IRepository 接口中的问题方法。

TDTO Find(Expression<Func<TModel, bool>> filter);

和

IEnumerable<TDTO> FindAll(Expression<Func<TModel, bool>> filter);

如您所见,其中有 TModel,用于构建表达式以过滤结果。我可以在 DTO 上使用表达式,但这需要映射到 DTO 的员工的完整列表(也就是说,它不会生成 SQL 过滤器,但会过滤 SELECT * FROM Employee 结果列表)。所以这不是一个好的选择。

另一个更可行但不是最佳的解决方案是使用动态 LINQ。这样,我可以在 Find / FindAll 方法中传递一个简单的字符串并摆脱 TModel 要求。但是,这意味着重构会变得很烦人,因为它会用魔术字符串填充代码。

【问题讨论】:

    标签: c# design-patterns repository-pattern dto unit-of-work


    【解决方案1】:

    就在我发布这篇文章时,我想我知道我的问题出在哪里了。

    Find() 和 FindAll() 甚至不应该存在。我应该在 IEmployeeRepository 中编写更具体的方法,例如 FindEmployeeByName(string name),然后像这样实现它:

    EmployeeDTO FindEmployeeByName(string name)
    {
        return Mapper.Map<EmployeeDTO>(dbSet.Where(o=>o.name.Contains(name)).FirstOfDefault());
    }
    

    任何人都可以确认这是一种正确的方法吗?或者提出更好的建议?

    编辑

    另外,如果我想保留 Find(Expression...) 和 FindAll(Expression...) 方法,我可以,但它们只是在数据层中,并由已实现的方法使用,以避免重复代码。但是它们不应该在控制器中使用,因为它们需要了解我的业务逻辑之外的底层数据结构。也就是说,它们可以在 BaseRepository 中实现(我已经有但没有提及以使事情更简单)并使EmployeesRepository 成为BaseRepository 的扩展。这样一来,每个 Repository 都已经有了模型感知的类泛型方法。

    不确定我是否正确解释了这一点。如果不清楚,请告诉我,我会尝试对其进行编辑并使其变得更好。

    【讨论】:

    • 这是一种方法,而且可能是耦合度最低的。以另一种方式查看我的答案。
    • 是的,很好。然而,通用 repos 很糟糕,我认为它们在 99.98% 的情况下都是反模式。关于 IEntityWithId ,命名是……嗯……是多余的,因为实体本身就具有 id。这就是使它成为一个实体的原因。
    • 我担心它会与 EF 命名空间中的某些内容发生冲突,但是是的,我没有考虑太多命名。为什么通用 repos 是反模式?
    • 存储库接口应该满足它所服务的有界上下文的需求。通用的东西意味着您所有的 BC 都有相同的需求。虽然它可能会发生,但这只是一个巧合。您应该让 Bc 指示存储库接口,而不是自动强加一个接口。事实上,当您使用通用 repo 时,很可能您有一个以 db 为中心的应用程序,现在被伪装成一个以 repo 为中心的应用程序。
    • 如果我的某个存储库不需要基本的 CRUD 操作,则 IXXXXXXRepository 可能不会实现通用存储库......现在我正在查看我的原始帖子,我意识到它不是我描述的。现在用我稍微偏离的结构“修复”它。
    【解决方案2】:

    另一种方法是让您的数据层依赖于业务层,并将您的存储库“项目”实体放入您的业务对象 (DTO)。这样你的 BL 在底层,UI 和 Data 依赖它,但 UI 和 Data 不相互依赖。

    这是 Mark Seemann 在他的依赖注入一书中所支持的方法。

    【讨论】:

    • 嗯,感谢您的回答,但这是我想要实现的目标,而不是我如何实现它。问题在于业务逻辑层中的 Find / FindAll 依赖于模型来构建过滤器表达式。我不打算让业务层依赖于数据层,但我不知道如何在保留过滤器表达式的同时避免它。但是,正如我在帖子中所描述的,问题在于我想要做什么,而不是我是如何尝试的。
    • @Pluc - 我只是为您提供了另一种设计应用程序的方法。另一种方法是创建另一个您的数据层和业务层都依赖的项目。该项目将仅包含您的实体和可能的某些接口定义。我的观点是,设计一个良好解耦的应用程序的方法不止一种。
    • 是的,但是您描述的模式与我在原始帖子中试图描述的模式相似/相同。我的模式很适合我正在做的事情,这只是我无法弄清楚的一个小细节。就像,这个概念很好,但我在某个地方搞砸了。从另一个图案设计重新开始可能会有所帮助,但如果我没有再犯同样的错误,那将是巧合。但无论哪种方式,感谢其他方式!
    猜你喜欢
    • 2013-06-13
    • 1970-01-01
    • 2011-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-06
    • 1970-01-01
    相关资源
    最近更新 更多