多线程应用程序中的单个 DbContext 实例
Single DbContext instance in multi-thread application
在我目前正在处理的 MVVM WPF 应用程序中,Entity Framework 6 的用法如下:
- 代码优先实体模型
- 一个 DbContext 在应用程序启动时创建并共享(通常在构造函数中传递)
- 某些实体的某些属性直接绑定到 UI - 或者是 NotMapped 并持有 UI 相关内容,例如。在此类实体的构造函数中创建的 UserControls
- 我们的应用程序有一个单独的线程刷新 "jobs" - 那些“作业”由外部应用程序直接写入数据库
- 有 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"
因此,我们在我们的上下文中为此实现了简单的锁定class,希望能解决这个问题:
public override int SaveChanges()
{
lock (this)
{
return base.SaveChanges();
}
}
这只是部分帮助,因为现在我们遇到以下异常,但出现频率较低:
"An error occurred while saving entities that do not expose foreign key properties for their relationships"
此外,我们有时会遇到链接属性的问题。尽管它们都被定义为 virtual 以启用延迟加载,但有时我们确实会遇到空引用异常,因为它们不会被链接。
我主要担心的是:
- 经过一些研究,我发现 EF 的这种实现并不像它应该的那样(上下文应该是短暂的)
- 在模型中具有 UI 绑定打破了 SoC 范例
- DbContext 不是线程安全的
我认为理想情况下我们应该重构架构——也许通过开发一些单独的层来处理这些问题,但在我们的情况下这会很耗时,而不是更好的解决方案。
有没有一种方法可以像我们的应用程序中已经设计的那样使用 DbContext 和 EF6,并进行一些更改来解决问题?
根据您的用例,启动查询时,使用 .AsNoTracking() 扩展方法可能会有所帮助。这使得 DbContext 不跟踪对象属性的更改,因此如果一个线程使用 .AsNoTracking() 获取数据而另一个线程更新一组可能冲突的数据(不使用 .AsNoTracking()),这两个操作不应干扰彼此。
你目前对问题的分析是正确的。 DbContexts 应该是短暂的。 UI 不应该绑定到实体,(即使 很多 的示例这样做,甚至来自 Microsoft)并且 DbContext 当然不是线程安全的。坏消息是不太可能有一个简单的方法来解决这个问题。
从一个长寿命 DbContext 切换到短寿命 DbContext 是一件相当微不足道的事情。但是,处理可能在 DbContext 实例之间传递的实体引用并不是那么简单。这直接融入了绑定到实体的第二个缺陷,因为这些实体从 domain/business 逻辑到视图并返回。短期上下文应该在很大程度上解决线程安全问题,只要实体不在上下文之间移动即可。
在已建立的应用程序中解决这样的问题可能很困难,具体取决于应用程序的成熟程度和时间压力,但这并非不可能。关键是确定应用程序中出现最多问题的区域,并首先解决这些问题。一个相当小但使用频繁的区域将是测试重构代码库所涉及的内容的理想选择。为避免破坏一切,您可以利用有界上下文定义将 DbContext 定义拆分为应用程序的每个区域,这样对于一组相关的视图,您可以组成一个视图模型结构并从一个新的、短暂的 DbContext 中填充它管理检索这些屏幕所需的所有实体,以及所有持久性。如果应用程序结构传递大量实体,那么困难的部分将是为您的代码建立边界。在这些边界处,方法签名将需要更改,以便原始 DbContext 中的实体不会流入新代码,并且调用旧代码可以将视图模型转换回原始 DbContext 范围内的实体。最终,尽管您使用的方法需要反映应用程序的确切结构。严重依赖抽象、泛型等的代码可能更难重构。
另一种方法是开始使用 Detach
/Attach
和 AsNoTracking
解决分离的实体。不过在我看来,这可能会让您陷入更深层次的复杂性,因此我会谨慎考虑将其作为可能的解决方案。 AsNoTracking
在您正在读取可能很大的记录子集并避免将它们关联到 DbContext 的成本的情况下是一个不错的选择。通常,DbContext 跟踪的实体越多,对上下文的操作就越慢。因此,搜索和报告等操作受益于 AsNoTracking
。但是,当 detaching/attaching 个实体时,您需要特意检查上下文实例以查看它在附加之前是否尚未跟踪具有相同 PK 的实例,并确保所提供的实体尚未被另一个 DbContext 跟踪。处理 detaching/attaching 嵌套实体图也很痛苦。
流行语录的变体。 "Someone that has a problem that they believe they can solve by using detached entities, now has two problems.":)
在我目前正在处理的 MVVM WPF 应用程序中,Entity Framework 6 的用法如下:
- 代码优先实体模型
- 一个 DbContext 在应用程序启动时创建并共享(通常在构造函数中传递)
- 某些实体的某些属性直接绑定到 UI - 或者是 NotMapped 并持有 UI 相关内容,例如。在此类实体的构造函数中创建的 UserControls
- 我们的应用程序有一个单独的线程刷新 "jobs" - 那些“作业”由外部应用程序直接写入数据库
- 有 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"
因此,我们在我们的上下文中为此实现了简单的锁定class,希望能解决这个问题:
public override int SaveChanges()
{
lock (this)
{
return base.SaveChanges();
}
}
这只是部分帮助,因为现在我们遇到以下异常,但出现频率较低:
"An error occurred while saving entities that do not expose foreign key properties for their relationships"
此外,我们有时会遇到链接属性的问题。尽管它们都被定义为 virtual 以启用延迟加载,但有时我们确实会遇到空引用异常,因为它们不会被链接。
我主要担心的是:
- 经过一些研究,我发现 EF 的这种实现并不像它应该的那样(上下文应该是短暂的)
- 在模型中具有 UI 绑定打破了 SoC 范例
- DbContext 不是线程安全的
我认为理想情况下我们应该重构架构——也许通过开发一些单独的层来处理这些问题,但在我们的情况下这会很耗时,而不是更好的解决方案。
有没有一种方法可以像我们的应用程序中已经设计的那样使用 DbContext 和 EF6,并进行一些更改来解决问题?
根据您的用例,启动查询时,使用 .AsNoTracking() 扩展方法可能会有所帮助。这使得 DbContext 不跟踪对象属性的更改,因此如果一个线程使用 .AsNoTracking() 获取数据而另一个线程更新一组可能冲突的数据(不使用 .AsNoTracking()),这两个操作不应干扰彼此。
你目前对问题的分析是正确的。 DbContexts 应该是短暂的。 UI 不应该绑定到实体,(即使 很多 的示例这样做,甚至来自 Microsoft)并且 DbContext 当然不是线程安全的。坏消息是不太可能有一个简单的方法来解决这个问题。
从一个长寿命 DbContext 切换到短寿命 DbContext 是一件相当微不足道的事情。但是,处理可能在 DbContext 实例之间传递的实体引用并不是那么简单。这直接融入了绑定到实体的第二个缺陷,因为这些实体从 domain/business 逻辑到视图并返回。短期上下文应该在很大程度上解决线程安全问题,只要实体不在上下文之间移动即可。
在已建立的应用程序中解决这样的问题可能很困难,具体取决于应用程序的成熟程度和时间压力,但这并非不可能。关键是确定应用程序中出现最多问题的区域,并首先解决这些问题。一个相当小但使用频繁的区域将是测试重构代码库所涉及的内容的理想选择。为避免破坏一切,您可以利用有界上下文定义将 DbContext 定义拆分为应用程序的每个区域,这样对于一组相关的视图,您可以组成一个视图模型结构并从一个新的、短暂的 DbContext 中填充它管理检索这些屏幕所需的所有实体,以及所有持久性。如果应用程序结构传递大量实体,那么困难的部分将是为您的代码建立边界。在这些边界处,方法签名将需要更改,以便原始 DbContext 中的实体不会流入新代码,并且调用旧代码可以将视图模型转换回原始 DbContext 范围内的实体。最终,尽管您使用的方法需要反映应用程序的确切结构。严重依赖抽象、泛型等的代码可能更难重构。
另一种方法是开始使用 Detach
/Attach
和 AsNoTracking
解决分离的实体。不过在我看来,这可能会让您陷入更深层次的复杂性,因此我会谨慎考虑将其作为可能的解决方案。 AsNoTracking
在您正在读取可能很大的记录子集并避免将它们关联到 DbContext 的成本的情况下是一个不错的选择。通常,DbContext 跟踪的实体越多,对上下文的操作就越慢。因此,搜索和报告等操作受益于 AsNoTracking
。但是,当 detaching/attaching 个实体时,您需要特意检查上下文实例以查看它在附加之前是否尚未跟踪具有相同 PK 的实例,并确保所提供的实体尚未被另一个 DbContext 跟踪。处理 detaching/attaching 嵌套实体图也很痛苦。
流行语录的变体。 "Someone that has a problem that they believe they can solve by using detached entities, now has two problems.":)