Entity Framework 的 Hangfire - DbContext 并发问题
Hangfire with Entity Framework - DbContext concurrency issues
我有一个 hangfire 服务负责两个工作:
- 创建带有相关附件的电子邮件对象(耗时的过程)
- 正在处理和发送
Pending
封电子邮件。
我正在使用 ASP.NET MVC (5.2.3),使用 StructureMap (4.5.1) 创建一个用于依赖注入的 IoC 容器,以及 Hangfire (1.6.19)
流程是:
- 电子邮件将被触发从前端创建,在前端创建即发即弃 hangfire 作业,以
Pending
状态将其推送到数据库中。
- 此过程使用一些存储库对象以 PDF 格式创建客户端报告(这些存储库使用
DbContext
从数据库中检索信息)
- 另一项重复性工作,收集所有
Pending
封电子邮件,并使用 System.Net.Mail
. 为每封电子邮件撰写一封电子邮件
这两项工作都很好,除非有多个工作同时创建电子邮件。
例如:如果前端同时触发了 10 个作业来生成电子邮件,作业将失败并显示错误:
A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.
抛出这个错误的那一行正在使用 await
,但我认为这是因为有不同的线程同时访问上下文:
var ids = await context.Objects
.Where(x => x.id == clientId && !x.IsDeleted)
.Select(x => x.id)
.ToListAsync();
我已经设置了 DbContext 尝试不同的范围,Transient
和 AlwaysUnique
,但错误仍然存在。
此外,这并不总是发生在同一行,基本上是在相关存储库中使用上下文的任何地方——如果两个作业同时命中同一行。
作业重试,电子邮件几乎每分钟发送一封,如果有数百封电子邮件要处理和发送(这就是我的情况下的问题),这可能无法正常工作
当我使用 ContainerScoped
LifeCycle 时问题已解决。
StructureMap Docs:
ContainerScoped in this case means that a registration will be built once per Container, such that the root container, any child or profile container, and every single nested container will build its own object instance
这意味着对于每个从 hangfire 中解雇的工作,都有一个新的 DataContext
实例(如果我理解正确,我可能会在更正的情况下说话)
特别是对于 Datacontext,我必须启用 MARS (Multiple Active Result Sets)
,这使 SQL 服务器允许在单个连接上执行多个批处理。
我有一个 hangfire 服务负责两个工作:
- 创建带有相关附件的电子邮件对象(耗时的过程)
- 正在处理和发送
Pending
封电子邮件。
我正在使用 ASP.NET MVC (5.2.3),使用 StructureMap (4.5.1) 创建一个用于依赖注入的 IoC 容器,以及 Hangfire (1.6.19)
流程是:
- 电子邮件将被触发从前端创建,在前端创建即发即弃 hangfire 作业,以
Pending
状态将其推送到数据库中。- 此过程使用一些存储库对象以 PDF 格式创建客户端报告(这些存储库使用
DbContext
从数据库中检索信息)
- 此过程使用一些存储库对象以 PDF 格式创建客户端报告(这些存储库使用
- 另一项重复性工作,收集所有
Pending
封电子邮件,并使用System.Net.Mail
. 为每封电子邮件撰写一封电子邮件
这两项工作都很好,除非有多个工作同时创建电子邮件。
例如:如果前端同时触发了 10 个作业来生成电子邮件,作业将失败并显示错误:
A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.
抛出这个错误的那一行正在使用 await
,但我认为这是因为有不同的线程同时访问上下文:
var ids = await context.Objects
.Where(x => x.id == clientId && !x.IsDeleted)
.Select(x => x.id)
.ToListAsync();
我已经设置了 DbContext 尝试不同的范围,Transient
和 AlwaysUnique
,但错误仍然存在。
此外,这并不总是发生在同一行,基本上是在相关存储库中使用上下文的任何地方——如果两个作业同时命中同一行。
作业重试,电子邮件几乎每分钟发送一封,如果有数百封电子邮件要处理和发送(这就是我的情况下的问题),这可能无法正常工作
当我使用 ContainerScoped
LifeCycle 时问题已解决。
StructureMap Docs:
ContainerScoped in this case means that a registration will be built once per Container, such that the root container, any child or profile container, and every single nested container will build its own object instance
这意味着对于每个从 hangfire 中解雇的工作,都有一个新的 DataContext
实例(如果我理解正确,我可能会在更正的情况下说话)
特别是对于 Datacontext,我必须启用 MARS (Multiple Active Result Sets)
,这使 SQL 服务器允许在单个连接上执行多个批处理。