当使用不同的dbContext跟踪对象时,我应该如何在WinForms中保持对象同步?

3
我有一个VB WinForms项目,我们使用Entity Framework 6 Code First。我知道在Web应用程序中限制dbContext(我们通过存储库类间接使用)的生命周期是理想的做法,但我最近了解到,通常情况下,对于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. 忽略Microsoft的建议,仅为特定的DB交互保持dbContext活动状态;在每次更新事件和表单加载时刷新dbContext(像Web应用程序一样)
  2. 从一个窗体传递相同的dbContext(在我的情况下,在存储库类的构造函数中),并且只在必要时进行处理(不是理想的做法-数据库由一些服务更新)
  3. 手动附加/分离并将更新标记为已更新
  4. 通过事件向其他窗体传递更新的对象,并在每个上下文中手动更新对象(我认为当需要保存更改时会遇到麻烦?)

1
在表单的生命周期中保留一个dbContext几乎是你不应该做的事情。这是我在桌面应用程序中进行数据库编程时必须学习的第一课(早在EF存在之前)。您需要使用每个dbContext将SELECT与UPDATE分开。如何在这种情况下防止静默数据覆盖才是真正的问题。 - glenebob
@glenebob 我也是这么想的,但微软的文档对于WindowsForms提出了不同的建议:https://learn.microsoft.com/en-us/ef/ef6/fundamentals/working-with-dbcontext#lifetime(第二个要点)...但是在非常有限的场景下,与表单之间的交互很少,似乎并没有太多意义。我会选择短暂的生命周期。我有业务规则和UI层规则,可以防止有意义的静默覆盖(这是一个只有1-2个安装位置的内部应用程序)。谢谢! - PTLEng
1
我曾经在一个项目中遇到了同样的问题。在我之前,开发者在每个类中使用了许多上下文。不过好的是,每个数据库函数都是独立运作的,它们使用相同的数据库对象,但根据需要打开和关闭连接。请记住,保持上下文活动状态并不一定是不好的,但是保持连接处于打开状态很可能会有问题。 - demoncrate
1个回答

1

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

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

使每个表单都拥有自己的上下文,永远不要在它们之间交换对象

这样,当您打开 ClassBForm 时,您不传递 ClassB ,而仅传递 Id ,然后新表单使用其自己的上下文加载对象。然后,在保存时,再次使用其自己的私有副本,当您发送通知事件时,您只通知 Id ,并且 ClassAForm 使用其上下文重新加载修改后的 ClassB 。两个表单除标识符外永远不交换数据,并尽可能保持分离。

根本不在表单中使用实体

实现DTOs/viewmodels/whatever以显示和修改数据。在数据层中,您使用ORM加载,然后返回另一个类(例如ClassADTO和ClassBDTO),其中包含其各自表单操作所需的所有信息。虽然您需要为每个表单定义定制的类,但是您可以确保ORM被留在DAOs中,不会干扰其他地方。DTOs只是POCOs,因此它们可以安全地传递,而不会产生任何不必要的结果。这样上下文仅在一个查询的持续时间内存在,就像在网页中一样。
关于您在问题中提出的解决方案,我认为它们都无法完全工作,或者至少需要特别注意边缘情况的错误:
忽略MS建议,仅在特定的DB交互中保持dbContexts处于活动状态;在每次更新事件和表单加载时刷新dbContext(s),就像它是Web应用程序一样。
只要您不想使用延迟加载或保存更改(在这种情况下会引发异常,因为已处理的上下文),这种方法就可以正常工作。您可能需要手动将对象附加到上下文中,以便在需要使用它们时使用,并注意可能会引起问题的每个延迟加载属性。
在我的情况下,从表单到表单传递相同的dbContext(在我的存储库类的构造函数中),很少处理它(不理想-数据库由几个服务更新)与理想情况完全相反。将两个表单绑定在一起会导致一个表单的更改影响另一个表单,您无法独立编辑两个对象,并且最重要的是,当您只想保存一个表单时,您最终会在两个地方保存更改。所有应该有所帮助的ORM功能最终都会伤害您。
手动附加/分离并在我的数据层中标记更新是可行的,但是跟踪每个对象的状态以及何时附加/分离是很多工作。分离的对象受到与短暂上下文一样的惰性加载问题的影响。很容易忘记附加对象并悄悄地出现不一致的更新。对我来说,这似乎是难以复制的错误的配方。
通过事件将已更新的对象传递到其他表单,并在每个上下文中手动更新对象(我假设当我需要保存更改时,这会遇到麻烦?)。
无论上下文如何排列,它的工作方式都会有所不同。对于共享上下文,您最终会在两种形式中得到完全相同的对象,在分离的上下文中,您最终会在下一次保存时获得不必要的更新,可能会覆盖更改。这就是为什么我建议只需使用正确的上下文重新加载并忽略来自其他表单的数据。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接