【问题标题】:How am I "supposed" to keep objects synced in WinForms when tracked by different dbContexts?当由不同的 dbContexts 跟踪时,我“应该”如何在 WinForms 中保持对象同步?
【发布时间】:2021-07-07 20:48:09
【问题描述】:

我有一个 VB WinForms 项目,我们使用 Entity Framework 6 代码优先。我知道在 Web 应用程序中限制 dbContexts 的生命周期(我们通过存储库类间接使用)是理想的,但我最近了解到,通常,对于 WinForms 项目,您应该在表单的生命周期内保留一个 dbContext .

假设我有 ClassA 类和 ClassB 类,并且所有 ClassA 对象都有一个可选的 ClassB 对象(以及 EF6 用作外键的相关“ClassBId”属性)。我们有一个表单 ClassAForm 供用户编辑和查看 ClassA 对象,这自然会显示有关其 ClassB 对象的一些信息;这个 ClassB 应该是 ClassA 独有的,但也可以在其他形式上查看。此表单上有一个按钮可以打开另一个表单 ClassBForm,它允许用户编辑和查看 ClassB 对象。 ClassBForm 不是模态的,目前不向生成它的 ClassAForm 传递任何内容,尽管 ClassAForm 确实有 ClassBForm 中的事件的侦听器。

因此,据我了解,ClassAForm 将获得它自己的长期存在的 dbContext 对象,它会在表单生命周期内保留该对象; ClassBForm 将获得它自己的 dbContext 对象。因此,如果我对 ClassBForm 的 dbContext 中的 ClassB 对象进行更改,在 ClassAForm 的 dbContext 版本的 ClassB 对象中查看这些更改的理想方法是什么,其中 ClassB 可能是 ClassA 的导航属性?

我可以想到一些解决这个问题的方法,但我不确定最佳做法是什么;也有可能(我希望有人会提到)我的整个理解或框架是不正确的。

(可能的)我想到的解决方案:

  1. 忽略 MS 的建议,只为特定的 DB 交互保持 dbContexts 活动;在每次更新事件和表单加载时刷新 dbContext(s),就像它是一个 Web 应用程序一样
  2. 在表单之间传递相同的 dbContext(s)(在我的存储库类的构造函数中,在我的例子中)并且很少处理它(不理想 - 数据库由几个服务更新)
  3. 在我的数据层中手动附加/分离并将更新标记为已更新
  4. 通过事件将更新的对象传递给其他表单,并在每个上下文中手动更新对象(我认为当我需要保存更改时这会遇到问题?)

【问题讨论】:

  • 在表单的整个生命周期中保留一个 dbContext 几乎是您不应该做的事情。这是我必须在桌面应用程序中学习数据库编程的第一堂课(早在 EF 存在之前)。您需要使用 dbContext 将 SELECT 与 UPDATE 分开。在这种情况下如何防止静默数据覆盖是真正的问题。
  • @glenebob 我也这么认为,但 MS 文档建议 WindowsForms 不然:docs.microsoft.com/en-us/ef/ef6/fundamentals/…(第二个要点)......但它似乎并不真正有意义有限的场景,表单之间的交互很少。我将带着短暂的生命去。我有应该防止有意义的静默覆盖的业务规则和 UI 层规则(这是一个只有 1-2 个安装位置的内部应用程序)。谢谢!
  • 我在一个项目中遇到了同样的问题。我之前的开发人员在每个课程中都使用了许多上下文。但好的是每个 DB 函数都单独工作,使用相同的 DB 对象,但按需打开和关闭连接。请记住,保持上下文活跃并不一定是坏事,但保持连接打开得很好。

标签: .net vb.net winforms entity-framework-6


【解决方案1】:

我认为这种情况对于 ORM 来说是最糟糕的情况之一,因为它们往往会干扰您对域实体对象执行的几乎所有操作,并产生错误或不需要的更改。

当遇到类似的问题时,我可以想到两种不同的可能解决方案:

让每个表单都有自己的上下文,并且从不在它们之间交换对象

这样,当您打开ClassBForm 时,您不会传递ClassB,而是仅传递Id,并且新表单使用自己的上下文来加载对象。然后在保存时,再次使用它自己的私有副本,当您发送通知事件时,您只通知IdClassAForm 使用其上下文再次重新加载修改后的ClassB。这两种形式从不交换标识符以外的数据,并尽可能保持独立。

不要在表单中使用实体

实施 DTO/viewmodels/任何东西来显示和修改数据**。在您使用 ORM 加载的数据层中,然后返回另一个类(例如 ClassADTOClassBDTO),它将包含操作其各自表单所需的所有信息。虽然您添加了一个额外的步骤并且需要定义为每个表单量身定制的类,但您要确保 ORM 不碍事,只在它所属的 DAO 中工作。 DTO 可以安全地传递而不会产生任何不想要的结果,因为它们只是 POCO。这样,上下文仅在一个查询期间存在,就像在网页中一样。

关于您在问题中提出的解决方案,我认为它们都不会完全起作用,或者至少需要仔细注意边缘情况的错误:

忽略 MS 的建议,只为特定的 DB 交互保持 dbContexts 处于活动状态;在每个更新事件和表单加载时刷新 dbContext(s),就像它是一个 Web 应用程序一样

只要您不想使用延迟加载或保存更改(由于已处置上下文而导致异常),此方法就可以正常工作。每当您想使用对象时,您可能需要手动将它们附加到上下文中,并注意每个可能会崩溃的延迟加载属性。

在表单之间传递相同的 dbContext(s)(在我的存储库类的构造函数中,在我的例子中)并且很少处理它(不理想 - 数据库由几个服务更新)

这与理想情况几乎相反。通过将两个表单绑定在一起,一个表单中的更改会影响另一个,您不能单独编辑两个对象,最重要的是,当您只想保存一个时,您最终会在两个位置保存更改。所有本应帮助您的 ORM 功能最终都会对您不利。

在我的数据层中手动附加/分离并将更新标记为已更新

这是可行的,但要跟踪每个对象的状态以及何时附加/分离,需要做大量工作。分离的对象会遇到与短期上下文相同的延迟加载问题。忘记附加对象并默默地以不一致的更新结束太容易了。对我来说,这似乎是难以重现错误的秘诀。

通过事件将更新的对象传递给其他表单,并在每个上下文中手动更新对象(我认为当我需要保存更改时这会遇到麻烦?)

根据上下文的排列方式,它的工作方式会有所不同。对于共享上下文,您最终会在两种表单中得到完全相同的对象,而使用不同的对象,您最终会在下次保存时得到不需要的更新,可能会覆盖更改。这就是为什么我建议使用适当的上下文重新加载并忽略来自其他表单的数据。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多