简体   繁体   English

Hangfire 事务处理(工作单元)

[英]Hangfire Transactional Process (Unit of Work)

In .Net Framework code which is below, it is ensured that someEntity object is inserted into db and Publish opertion will be executed after.在下面的 .Net Framework 代码中,确保将 someEntity 对象插入到 db 中,然后执行 Publish 操作。 However, in .Net Core I could not manage to do this.但是,在 .Net Core 中,我无法做到这一点。 When I try to run this piece of code, Platform Exception occurs.当我尝试运行这段代码时,发生平台异常。

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();
}

Is there any known good solution for this situation?对于这种情况,是否有任何已知的好的解决方案?

Note: .Net Core 2.2 Console application, EntityFrameworkCore 2.1 and Hangfire 1.6.21 are used for testing注意:.Net Core 2.2 Console 应用,EntityFrameworkCore 2.1 和 Hangfire 1.6.21 用于测试


Update : Whole stacktrace更新:整个堆栈跟踪

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

This is now working when using EntityFramework Core 3.0 or greater.这现在在使用 EntityFramework Core 3.0 或更高版本时有效。 The reason this did not work in EFCore 2.x was that EFCore 2.x did not close the connection when not used.这在 EFCore 2.x 中不起作用的原因是 EFCore 2.x 在不使用时没有关闭连接。 Instead the DbConnection was kept open until the context was disposed.相反,DbConnection 一直保持打开状态,直到上下文被处理。

This behavior has changed in EF Core 3.0: https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes#database-connection-is-now-closed-if-not-used-anymore-before-the-transactionscope-has-been-completed这种行为在 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 -关闭如果未使用-在交易范围已经完成之前

Starting with 3.0, EF Core closes the connection as soon as it's done using it.从 3.0 开始,EF Core 会在使用完毕后立即关闭连接。 This enables your scenario, where you want to enlist EfCore and Hangfire in the same transaction, without escalating to MSDTC.这支持您的方案,您希望在同一事务中登记 EfCore 和 Hangfire,而不升级到 MSDTC。

It looks like a distrubutrd transaction is beeing initiated, abd that is not supported under .net core.看起来正在启动 distrubutrd 事务,在 .net core 下不支持 abd。

Since you are accessing more than one resource manager (your db and hangfire's db) the transactionscope tries to escalate the transaction to be ditributed.由于您正在访问多个资源管理器(您的数据库和 hangfire 的数据库),因此事务范围会尝试升级要分发的事务。

You can take hangfire's _backgroundJobClient.Enqueue() out of the scope , and so the escalation will not happen.您可以将_backgroundJobClient.Enqueue()_backgroundJobClient.Enqueue()移出 scope ,因此不会发生升级。

You will have to find another way to assure both actions are performed (db update, hangfire enqueue)您必须找到另一种方法来确保执行这两个操作(数据库更新、挂火入队)

EDIT : since you cannot have a transaction you have to design your services to handle the possible failure situations.编辑:由于您不能进行交易,因此您必须设计您的服务来处理可能的故障情况。 for instance: The account service will:例如:帐户服务将:

  1. Persist the created used to the db将创建的用于数据库的持久化

  2. Then call hangfire Enqueue然后调用hangfire Enqueue

  3. Log the fact that the hangfire job was created in the same user db.记录 hangfire 作业是在同一个用户数据库中创建的事实。

  4. the user service must poll the db, to see if there were users created but a notification was not logged.用户服务必须轮询数据库,以查看是否创建了用户但未记录通知。

The other micro services, should be able to handle duplicate notifications.其他微服务应该能够处理重复通知。

in this way , if a user was created but a notification was not sent, your service will resend (4).这样,如果创建了用户但未发送通知,您的服务将重新发送 (4)。

The receiving services will ignore a duplicate request if the failure happens between (2) and (3)如果失败发生在 (2) 和 (3) 之间,接收服务将忽略重复的请求

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM