在下面的.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
现在使用 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。
看起来正在启动 distrubutrd 事务,但 .net core 不支持 abd。
由于您正在访问多个资源管理器(您的数据库和hangfire的数据库),transactionscope会尝试升级要分配的事务。
您可以将hangfire的
_backgroundJobClient.Enqueue()
移出范围,这样升级就不会发生。
您必须找到另一种方法来确保执行这两个操作(数据库更新、hangfire 排队)
编辑: 由于您无法进行交易,因此您必须设计服务来处理可能的失败情况。例如: 帐户服务将:
将创建的内容保留到数据库
然后调用hangfire Enqueue
记录hangfire作业是在同一用户数据库中创建的事实。
用户服务必须轮询数据库,以查看是否创建了用户但未记录通知。
其他微服务应该能够处理重复的通知。
这样,如果创建了用户但未发送通知,您的服务将重新发送(4)。
如果在 (2) 和 (3) 之间发生故障,接收服务将忽略重复请求
可在 .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 或更多