C# 多线程应用程序中的单个DbContext实例

C# 多线程应用程序中的单个DbContext实例,c#,wpf,multithreading,entity-framework,dbcontext,C#,Wpf,Multithreading,Entity Framework,Dbcontext,在我目前正在开发的MVVM WPF应用程序中,实体框架6的使用方式如下: 代码优先实体模型 一个DbContext在应用程序启动时创建,并在多个应用程序之间共享(通常在构造函数中传递) 某些实体的某些属性直接绑定到UI,或者未映射并保存与UI相关的内容,例如在此类实体的构造函数中创建的用户控件 我们的应用程序有一个单独的线程刷新“作业”——这些“作业”由外部应用程序直接写入数据库 有一个UI线程正在使用相同的DbContext在UI中添加、删除或更改这些“作业”和其他实体的单击操作 还有另一个

在我目前正在开发的MVVM WPF应用程序中,实体框架6的使用方式如下:

  • 代码优先实体模型
  • 一个DbContext在应用程序启动时创建,并在多个应用程序之间共享(通常在构造函数中传递)
  • 某些实体的某些属性直接绑定到UI,或者未映射并保存与UI相关的内容,例如在此类实体的构造函数中创建的用户控件
  • 我们的应用程序有一个单独的线程刷新“作业”——这些“作业”由外部应用程序直接写入数据库
  • 有一个UI线程正在使用相同的DbContext在UI中添加、删除或更改这些“作业”和其他实体的单击操作
  • 还有另一个单独的线程用于刷新和管理其他实体
  • 使用延迟加载的优势,这些实体彼此链接
首先,我们在context.SaveChanges()方面遇到了问题-我们遇到了各种不同的异常,例如:

"New transaction is not allowed because there are other threads running in the session"

"The property "ID" is part of the object's key information and cannot be modified"

"The transaction operation cannot be performed because there are pending requests working on this transaction"
因此,我们在上下文类中对此实现了简单锁定,希望解决此问题:

public override int SaveChanges()
{
锁(这个)
{
返回base.SaveChanges();
}
}
这只起到了部分作用,因为现在我们遇到了以下异常,但出现的频率较低:

"An error occurred while saving entities that do not expose foreign key properties for their relationships"
此外,我们有时会遇到链接属性的问题。即使它们都被定义为虚拟的以支持延迟加载,有时我们也会得到一个空引用异常,因为它们不会被链接

我主要关注的是:

  • 经过一些研究,我发现EF的这种实现不是它应该的(上下文应该是短暂的)
  • 模型中的UI绑定打破了SoC范式
  • DbContext不是线程安全的
我认为,理想情况下,我们应该重构体系结构——也许可以通过开发一些单独的层来处理这些问题,但在我们的情况下,这将非常耗时,而不是更好的解决方案


是否有一种方法可以像我们的应用程序中已经设计的那样使用DbContext和EF6,并进行一些更改以解决问题?

根据您的用例,在启动查询时,使用.AsNoTracking()扩展方法可能会有所帮助。这使得DbContext不会跟踪对象属性的更改,因此,如果一个线程使用.AsNoTracking()获取数据,而另一个线程更新可能冲突的数据集(不使用.AsNoTracking()),则这两个操作不应相互干扰。

您目前对问题的分析是正确的。DBContext应该是短期的。UI不应该绑定到实体(即使很多示例都这样做,甚至来自Microsoft),DbContext当然不是线程安全的。坏消息是,不太可能有一个简单的解决办法

从一个长寿命的DbContext切换到短命的DbContext是一件相当简单的事情。但是,处理可能在DbContext实例之间传递的实体引用并不是那么简单。这直接与绑定到实体的第二个缺陷混合在一起,因为这些实体从域/业务逻辑到查看和返回。只要实体不在上下文之间移动,短期上下文应该在很大程度上解决线程安全问题

在已建立的应用程序中解决这样的问题可能很困难,这取决于应用程序的成熟程度和时间压力,但这并非不可能。关键是确定应用程序中出现最多问题的领域,并首先解决这些问题。一个相当小,但大量使用的区域将是理想的测试水域,它将涉及到重新考虑代码库的因素。为了避免破坏一切,您可以利用一个有界上下文定义将DbContext定义拆分为应用程序的每个区域,以便对于一组相关视图,您可以组成一个视图模型结构,并从一个新的、短期的DbContext中填充它,该DbContext将管理检索这些屏幕所需的所有实体和所有持久性。如果应用程序结构正在传递大量实体,那么最困难的部分将是为代码建立一个周界。在这些边界处,方法签名需要更改,以便原始DbContext中的实体不会流入新代码,并且对旧代码的调用可以将视图模型转换回原始DbContext范围内的实体。最终,您使用的方法需要反映应用程序的确切结构。严重依赖抽象、泛型等的代码可能更难重新考虑

另一种方法是使用
Detach
/
Attach
AsNoTracking
开始处理分离的实体。不过,在我看来,这可能会让你陷入一个更为复杂的兔子洞,因此我会谨慎考虑将其视为一个可能的解决方案<代码>AsNoTracking是一个很好的选择,用于从可能较大的记录子集中读取数据,并避免将它们与DbContext关联的成本。通常,DbContext跟踪的实体越多,针对上下文的操作就越慢。因此,搜索和报告等操作受益于
AsNoTracking
。但是,在分离/附加实体时,需要仔细检查上下文实例,以查看它在附加之前是否已经跟踪具有相同PK的实例,并确保提供的实体没有被另一个DbContext跟踪。处理分离/附加嵌套实体图也是一个难题

流行语录的变体。“有人认为他们可以通过使用分离实体解决问题,不是吗