Hangfire 事务流程(工作单元)

问题描述 投票:0回答:3

在下面的.Net Framework代码中,确保将someEntity对象插入到数据库中,然后执行发布操作。然而,在.Net Core 中我无法做到这一点。当我尝试运行这段代码时,发生平台异常。

using (var transaction = new TransactionScope())
{
    SomeEntity someEntity = new SomeEntity();
    someEntity.Gui = Guid.NewGuid().ToString();

    _dataContext.SomeEntities.Add(someEntity);
    _dataContext.SaveChanges();

    _backgroundJobClient.Enqueue(() => PublishSomeEntityCreatedEvent(someEntity.Id)));

    transaction.Complete();
}

对于这种情况有什么好的解决方案吗?

注意:.Net Core 2.2 控制台应用程序、EntityFrameworkCore 2.1 和 Hangfire 1.6.21 用于测试


更新:整个堆栈跟踪

Hangfire.BackgroundJobClientException: Background job creation failed. See inner exception for details. ---> System.PlatformNotSupportedException: This platform
 does not support distributed transactions.
   at System.Transactions.Distributed.DistributedTransactionManager.GetDistributedTransactionFromTransmitterPropagationToken(Byte[] propagationToken)
   at System.Transactions.TransactionInterop.GetDistributedTransactionFromTransmitterPropagationToken(Byte[] propagationToken)
   at System.Transactions.TransactionStatePSPEOperation.PSPEPromote(InternalTransaction tx)
   at System.Transactions.TransactionStateDelegatedBase.EnterState(InternalTransaction tx)
   at System.Transactions.EnlistableStates.Promote(InternalTransaction tx)
   at System.Transactions.Transaction.Promote()
   at System.Transactions.TransactionInterop.ConvertToDistributedTransaction(Transaction transaction)
   at System.Transactions.TransactionInterop.GetExportCookie(Transaction transaction, Byte[] whereabouts)
   at System.Data.SqlClient.SqlInternalConnection.GetTransactionCookie(Transaction transaction, Byte[] whereAbouts)
   at System.Data.SqlClient.SqlInternalConnection.EnlistNonNull(Transaction tx)
   at System.Data.SqlClient.SqlInternalConnection.Enlist(Transaction tx)
   at System.Data.SqlClient.SqlInternalConnectionTds.Activate(Transaction transaction)
   at System.Data.ProviderBase.DbConnectionInternal.ActivateConnection(Transaction transaction)
   at System.Data.ProviderBase.DbConnectionPool.PrepareConnection(DbConnection owningObject, DbConnectionInternal obj, Transaction transaction)
   at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection)
   at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal& connection)
   at System.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection)
   at System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
   at System.Data.ProviderBase.DbConnectionClosed.TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
   at System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry)
   at System.Data.SqlClient.SqlConnection.Open()
   at Hangfire.SqlServer.SqlServerStorage.CreateAndOpenConnection()
   at Hangfire.SqlServer.SqlServerStorage.UseConnection[T](DbConnection dedicatedConnection, Func`2 func)
   at Hangfire.SqlServer.SqlServerConnection.CreateExpiredJob(Job job, IDictionary`2 parameters, DateTime createdAt, TimeSpan expireIn)
   at Hangfire.Client.CoreBackgroundJobFactory.Create(CreateContext context)
   at Hangfire.Client.BackgroundJobFactory.<>c__DisplayClass7_0.<CreateWithFilters>b__0()
   at Hangfire.Client.BackgroundJobFactory.InvokeClientFilter(IClientFilter filter, CreatingContext preContext, Func`1 continuation)
   at Hangfire.Client.BackgroundJobFactory.<>c__DisplayClass7_1.<CreateWithFilters>b__2()
   at Hangfire.Client.BackgroundJobFactory.CreateWithFilters(CreateContext context, IEnumerable`1 filters)
   at Hangfire.Client.BackgroundJobFactory.Create(CreateContext context)
   at Hangfire.BackgroundJobClient.Create(Job job, IState state)
   --- End of inner exception stack trace ---
   at Hangfire.BackgroundJobClient.Create(Job job, IState state)
   at Hangfire.BackgroundJobClientExtensions.Create(IBackgroundJobClient client, Expression`1 methodCall, IState state)
   at Hangfire.BackgroundJobClientExtensions.Enqueue(IBackgroundJobClient client, Expression`1 methodCall)
   at TopShelf_Hangfire_NetCore.BusinessService.Execute(DateTime utcNow) in C:\Projects\Practices\TopShelf_Hangfire_NetCore\BusinessService.cs:line 31
   at TopShelf_Hangfire_NetCore.StartupService._timer_Elapsed(Object sender, ElapsedEventArgs e) in C:\Projects\Practices\TopShelf_Hangfire_NetCore\StartupService.cs:line 35
c# .net-core entity-framework-core transactionscope hangfire
3个回答
2
投票

现在使用 EntityFramework Core 3.0 或更高版本时可以使用此功能。 这在 EFCore 2.x 中不起作用的原因是 EFCore 2.x 在不使用时不会关闭连接。相反,DbConnection 保持打开状态,直到上下文被释放。

此行为在 EF Core 3.0 中已更改:https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/writing-changes#database-connection-is -如果在交易范围已完成之前不再使用,则现在关闭

从 3.0 开始,EF Core 在使用完毕后立即关闭连接。 这可以实现您希望在同一事务中加入 EfCore 和 Hangfire 的场景,而无需升级到 MSDTC。


0
投票

看起来正在启动 distrubutrd 事务,但 .net core 不支持 abd。

由于您正在访问多个资源管理器(您的数据库和hangfire的数据库),transactionscope会尝试升级要分配的事务。

您可以将hangfire的

_backgroundJobClient.Enqueue()
移出范围,这样升级就不会发生。

您必须找到另一种方法来确保执行这两个操作(数据库更新、hangfire 排队)

编辑: 由于您无法进行交易,因此您必须设计服务来处理可能的失败情况。例如: 帐户服务将:

  1. 将创建的内容保留到数据库

  2. 然后调用hangfire Enqueue

  3. 记录hangfire作业是在同一用户数据库中创建的事实。

  4. 用户服务必须轮询数据库,以查看是否创建了用户但未记录通知。

其他微服务应该能够处理重复的通知。

这样,如果创建了用户但未发送通知,您的服务将重新发送(4)。

如果在 (2) 和 (3) 之间发生故障,接收服务将忽略重复请求


0
投票

可在 .net 8 中使用最新版本的 ef core 和hangfire。 (在 postgres 中进行了测试,因为hangfire将所有必要的数据存储在同一个数据库中,但在单独的模式中)

由于 transactionScope 是环境变量,因此我们能够在保存到主表和使用 Hangfire 设置后台作业之间创建事务。

using (var transactionScope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
    _dbContext.Persons.Add(new Person());
    
    _backgroundJobClient.Enqueue(() => Console.WriteLine("Call hagfire background job."));
    
    await _dbContext.SaveChangesAsync();
    
    transactionScope.Complete();
}

注意: 你必须允许 postgres 在配置中使用准备好的事务。 为此,请打开 postgresql.conf 文件并将 max_prepared_transactions 设置为 1 或更多

© www.soinside.com 2019 - 2024. All rights reserved.