Транзакционный процесс Hangfire (единица работы) - PullRequest
0 голосов
/ 17 января 2019

В приведенном ниже коде .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

1 Ответ

0 голосов
/ 17 января 2019

Похоже, что инициируется транзакция distrubutrd, abd, которая не поддерживается в ядре .net.

Так как вы обращаетесь к нескольким менеджерам ресурсов (ваша база данных и база данных hangfire), транзакция пытается увеличить транзакцию, которая будет распределена.

Вы можете вывести _backgroundJobClient.Enqueue() Hangfire из области видимости, и таким образом эскалация не произойдет.

Вам нужно будет найти другой способ убедиться, что оба действия выполнены (обновление базы данных, постановка очереди на зависание)

EDIT : так как у вас не может быть транзакции, вы должны разработать свои сервисы для обработки возможных ситуаций сбоя. например: Служба аккаунта будет:

  1. Сохранение созданного использованного в БД

  2. Затем вызовите Enqueue

  3. Записать тот факт, что задание зависания было создано в том же БД пользователя.

  4. служба пользователя должна опросить базу данных, чтобы увидеть, были ли созданы пользователи, но уведомление не было зарегистрировано.

Другие микро-сервисы должны иметь возможность обрабатывать дубликаты уведомлений.

таким образом, если пользователь был создан, но уведомление не было отправлено, ваша служба будет повторно отправлена ​​(4).

Принимающие сервисы будут игнорировать дубликат запроса, если сбой случится между (2) и (3)

...