Использование одной транзакции в нескольких DbContext
Редактировал(а) Alexandr Fokin 2023/11/11 14:11
Взаимодействие с транзакциями | |
Использование одной транзакции в нескольких DbContext. Cross-context transactions. | Необходимо распространить компоненты:
|
public class ContextFactory { private readonly TransactionScopeProvider TransactionScopeProvider; public ContextFactory( TransactionScopeProvider transactionScopeProvider ) { TransactionScopeProvider = transactionScopeProvider; } #region public Context Create() { var context = new Context(); TransactionScopeProvider.SetTransactionContextIfExsist( context ); return context; } /// <summary> /// Выполнить транзакцию /// </summary> /// <param name="action">действие</param> /// <param name="shareToScope">Задать контекст транакций</param> public async Task ExecuteTransactionAsync( Func<Context, Task<bool>> action, bool shareToScope, CancellationToken token = default ) { using var context = Create(); await context.ExecuteTransactionAsync( async (c, t) => { if (shareToScope) { TransactionScopeProvider.SetFromContext(c); } return await action(c); }, token ); } #endregion } public class TransactionScopeProvider { private readonly AsyncLocal<Container?> Transaction = new AsyncLocal<Container?>(); public Context SetTransactionContextIfExsist( Context context ) { if (!TryGet(out var tran)) { return context; } context.Database.SetDbConnection(tran!.Connection); context.Database.UseTransaction( tran!.Transaction.GetDbTransaction() ); return context; } public bool IsSet() { return Transaction.Value != null; } public void Set( Container tran ) { if (IsSet()) { throw new NotSupportedException(""); } Transaction.Value = tran; } public void SetFromContext( Context context ) { var container = new Container() { Connection = context.Database.GetDbConnection(), Transaction = context.Database.CurrentTransaction! }; Set(container); } public bool TryGet(out Container? tran) { if (!IsSet()) { tran = null; return false; } tran = Transaction.Value; return true; } public Container Get() { if (!TryGet(out var tran)) { throw new Exception("Transaction context is not set"); } return tran!; } #region public record Container { public System.Data.Common.DbConnection Connection { get; init; } = null!; public IDbContextTransaction Transaction { get; init; } = null!; } #endregion } public class Context{ //... public async Task ExecuteTransactionAsync( Func<Context, IDbContextTransaction, Task<bool>> action, CancellationToken token = default ) { using var transaction = await Database.BeginTransactionAsync(token); try { var result = await action(this, transaction); if (result) { await transaction.CommitAsync(token); } else { await transaction.RollbackAsync(token); } } catch(Exception) { await transaction.RollbackAsync(token); throw; } } } |