Использование одной транзакции в нескольких DbContext

Редактировал(а) Alexandr Fokin 2023/11/11 14:11

 Взаимодействие с транзакциями
Использование одной транзакции в нескольких DbContext.
Cross-context transactions.

Необходимо распространить компоненты:

  • System.Data.Common.DbConnection
  • System.Data.Common.DbTransaction
 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;
  }
 }
}

 

Теги: