簡體   English   中英

Hangfire 事務處理(工作單元)

[英]Hangfire Transactional Process (Unit of Work)

在下面的 .Net Framework 代碼中,確保將 someEntity 對象插入到 db 中,然后執行 Publish 操作。 但是,在 .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 Console 應用,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 : //docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/break-changes#database-connection-is-now -關閉如果未使用-在交易范圍已經完成之前

從 3.0 開始,EF Core 會在使用完畢后立即關閉連接。 這支持您的方案,您希望在同一事務中登記 EfCore 和 Hangfire,而不升級到 MSDTC。

看起來正在啟動 distrubutrd 事務,在 .net core 下不支持 abd。

由於您正在訪問多個資源管理器(您的數據庫和 hangfire 的數據庫),因此事務范圍會嘗試升級要分發的事務。

您可以將_backgroundJobClient.Enqueue()_backgroundJobClient.Enqueue()移出 scope ,因此不會發生升級。

您必須找到另一種方法來確保執行這兩個操作(數據庫更新、掛火入隊)

編輯:由於您不能進行交易,因此您必須設計您的服務來處理可能的故障情況。 例如:帳戶服務將:

  1. 將創建的用於數據庫的持久化

  2. 然后調用hangfire Enqueue

  3. 記錄 hangfire 作業是在同一個用戶數據庫中創建的事實。

  4. 用戶服務必須輪詢數據庫,以查看是否創建了用戶但未記錄通知。

其他微服務應該能夠處理重復通知。

這樣,如果創建了用戶但未發送通知,您的服務將重新發送 (4)。

如果失敗發生在 (2) 和 (3) 之間,接收服務將忽略重復的請求

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM