使用实体框架时对将业务逻辑放在哪里感到困惑

Posted

技术标签:

【中文标题】使用实体框架时对将业务逻辑放在哪里感到困惑【英文标题】:Confusion about where to put business logic when using Entity framework 【发布时间】:2013-01-15 17:56:30 【问题描述】:

我刚刚开始使用实体框架,我对业务层中的类通常如何与实体框架创建的实体相适应感到困惑。

当使用经典的 ADO.NET 时,我将有一个名为 Customer 的类,然后是另一个名为 DALCustomer 的类来处理数据库交互,在这种结构中,我会将代码用于计算、过滤和删除一个实例用于在 Customer 类中保存、更新和删除的 DAL。

使用实体框架,如果您有一个名为 Customer 的表,实体框架会创建一个名为 Customer 的实体,这就是我开始困惑的地方,这个实体是否消除了业务层中对 Customer 的需求?那么本质上,通常在业务层中的所有字段和方法都在实体框架生成的实体中?还是应该在业务层中仍然存在一个名为 CustomerBL 的类,例如,它仍然包含完成计算、过滤所需的业务逻辑所需的字段和方法,并且仍然需要声明的 EF DAL 实例来处理数据访问?

如果应该有一个业务类,在这种情况下是 CustomerBL,另一个问题会跳到脑海中,是否应该在 CustomerBL 中重新创建在客户实体中创建的字段,或者是否应该在 CustomerBL 中声明客户实体的实例?不需要在 2 个位置声明字段吗?

【问题讨论】:

【参考方案1】:

实体框架的设计考虑了数据模型和概念模型之间的分离。它支持inheritance、entity splitting(非 EF 核心)、table splitting、complex types 或owned types 和transparent many-to-many associations(非 EF 核心),所有这些都允许将域模型塑造成一个人的需要而无需受数据存储模型限制太多。 EF 核心支持阴影属性,可用于隐藏横切关注点from the exposed class model。

代码优先的方法允许使用 POCO,其中只有少数属性映射到数据存储列,而其他属性则服务于域目标。模型优先和数据库优先生成部分类,允许扩展生成的代码。

当然,这种概念模型和商店模型的分离只能在一定程度上取得成功。有些事情不利于持久性无知的目标。比如——

如果需要延迟加载,则需要将导航属性声明为 virtual,以便 EF 可以在代理类型中覆盖它们。领域驱动设计 (DDD) 鼓励仅在需要多态性时使用 virtual

在“真实”关联(Parent 参考)附带原始外键属性(例如,ParentId)非常方便。纯粹主义者认为这违反了 DDD 原则。

EF 类模型将是数据访问层的一部分,并且应该主要服务于该目标。因此,它将包含许多互惠关系,以便在编写 LINQ 查询时尽可能地受益于导航属性。这些相互关系又违反了 DDD 原则。

有大量的differences between LINQ-to-objects and LINQ-to-entities。您不能忽略这样一个事实,即您正在针对与内存中的对象完全不同的宇宙进行 LINQ-ing。这称为tight coupling, or leaky abstraction。

EF 只能映射具体类,不能映射接口。

但是...通常我很乐意使用从代码优先模型生成的 EF 类或 POCO 作为域类。到目前为止,我从未见过从一个数据存储或 ORM 到另一个的无摩擦过渡,如果它发生的话。执着无知是虚构的。 DAL 的特质很容易在领域中留下足迹。只有当您必须为不同的数据存储/模型编写代码时,或者当存储/模型预计会相对频繁地更改时,才需要尽可能减少这种占用空间或将其完全抽象出来。

另一个可能将 EF 类提升为域类的因素是当今许多应用程序具有多个层,其中(序列化)不同的视图模型或 DTO 被发送到客户端。在 UI 中使用域类几乎不符合要求。您也可以将 EF 类模型用作域,并让服务根据 UI 或服务使用者的要求返回专用模型和 DTO。如果只是在性能方面,另一个抽象层可能更像是一种负担而不是一种祝福。

【讨论】:

我不明白您所说的resists the temptation to rely on things EF puts in there, like INotifyPropertyChanged 是什么意思?如果需要,可以修改 t4 模板以实现类似的功能。 当然可以修改。我的意思是,使用这些 EF 生成的属性和方法会进一步打破持久性无知。并且标准模板可能会随着新版本的变化而变化。 Gert,没有不尊重的意思,但你能否加入我的水平,也许让我知道你所说的如何适用于我原始帖子中的示例,因为我没有完全关注你,因为毕竟我是 EF 的新手。 没关系,老大!这一切都归结为一个建议(我的意见):按原样使用 EF 类。这是Customer 类,而不是额外的CustomerBL 类。使用分部类来扩展生成的具有业务逻辑的类。 所以,如果我理解正确的话,放弃 UI、BL 和 DAL 的传统物理分离,本质上将 BL 和 DAL 合并为一个利用部分类,本质上就是它的要点?【参考方案2】:

在我看来,使用 POCO 作为可以持久化的实体的全部意义在于消除“数据库实体”和“业务实体”之间的区别。 “实体”应该是“业务实体”,可以直接从数据存储中持久化和加载,因此同时充当“数据库实体”。通过使用 POCO,业务实体与特定机制分离以与数据库交互。

您可以将实体移动到一个单独的项目中 - 例如 - 该项目没有对任何 EF 程序集的引用,但在数据库层项目中使用它们来管理持久性。

这并不意味着您可以在完全不考虑 EF 要求的情况下设计您的业务实体。当您使用 EF 将业务实体映射到数据库模式时,您需要了解一些限制以避免麻烦,例如:

您必须使导航属性(对其他实体的引用或引用集合)virtual 以支持使用 EF 进行延迟加载 您不能将IEnumerable<T> 用于必须持久化的集合。它必须是 ICollection<T> 或更派生的类型。 坚持private属性并不容易 EF 不支持char 类型,如果要保留其值,则不能使用它 还有更多...

但在我看来,另外一组实体是额外的复杂层,如果上述限制对您的项目来说过于严格,则应该有理由真正需要它。

YA2C(又是 2 美分 :))

【讨论】:

我也同意这一点,它是 ORM 的底层机制“替换”了数据层而不是实体本身。在 MVC 类型的应用程序中,我可以根据需要创建单独的 ViewModel 来表达不是从实体派生的特定信息。就像 OP 一样,虽然我有时仍然觉得这很难导航。【参考方案3】:

我不知道其他人是否认为这是一种好的做法,但我个人过去是这样处理的:

EF 生成的类是您的 DAL,然后为 BL 创建一组互补的类,您将在其中拥有您需要的结构(比如可能以一对一的关系合并来自相关实体的数据)和其他业务逻辑处理关注点(自定义验证,例如实现 IDataErrorInfo 以使其与 WPF 中的 UI 配合使用)并创建包含与实体类型相关的所有业务层方法的类,这些方法使用 BL 实例并转换为从 EF 实体到 BL 对象。

因此,例如,您的数据库中有 Customer。 EF会生成一个类Customer,在BL中会有一个Customer(前缀、后缀等)类和一个CustomerLogic类。在 BL Customer 类中,您可以做任何需要满足需求的事情,而不必篡改 EF 实体,在 CustomerLogic 类中,您将拥有 BL 方法(加载最有价值的客户、为客户保存额外数据等)。

现在,这使您能够与数据源实现松散耦合。过去(在 WPF 项目中)这使我受益的另一个示例是,您可以执行 IDataErrorInfo 之类的操作并在 CustomerBL 类中实现验证逻辑,这样当您将实体绑定到 UI 上的创建/编辑表单时您将受益于 WPF 提供的内置功能。

...我的 2 美分,我也很想知道什么是最佳实践或其他解决方案/观点是什么。


也可能与这个话题有关 - Code-first vs Model/Database-first

【讨论】:

你将如何处理由 EF 生成并在 BL 类中需要的字段?你能提供一些你的方法的样本吗? 好吧,我写的所有内容都缺少一个事实,即您需要将数据复制到/从 BL/DAL 实体的方法,并且您通过实现存储库模式将自己与 DAL 进一步隔离这样您就不会直接访问 ObjectSet 本身,而是调用存储库进行 CRUD 操作【参考方案4】:

这个话题可能有点老了,但这可能会有所帮助。 Andras Nemes 在他的博客中指出了使用 DDD(领域驱动设计)而不是 EF、MVC 等技术驱动设计的担忧。

http://dotnetcodr.com/2013/09/12/a-model-net-web-service-based-on-domain-driven-design-part-1-introduction/

【讨论】:

【参考方案5】:

我使用业务逻辑来编写我的方法并在其创建的视图中返回结果,例如:

namespace Template.BusinessLogic

    public interface IApplicantBusiness
    
        List<Template.Model.ApplicantView> GetAllApplicants();

        void InsertApplicant(Template.Model.ApplicantView applicant);
    

【讨论】:

以上是关于使用实体框架时对将业务逻辑放在哪里感到困惑的主要内容,如果未能解决你的问题,请参考以下文章

我在哪里使用 Core Data 将业务逻辑放在 IOS 应用程序中?

将两个相关对象的业务逻辑放在哪里?

Lumen 中的业务逻辑应该放在哪里?

业务逻辑是指控制器或模型。请详细说明[重复]

在业务逻辑层使用实体框架生成的类

在单独的数据访问和业务逻辑层中,我可以在业务层中使用实体框架类吗?