using System.Text.Json; using System.Transactions; using BuildingBlocks.Core; using BuildingBlocks.PersistMessageProcessor; using BuildingBlocks.Polly; using MediatR; using Microsoft.Extensions.Logging; namespace BuildingBlocks.EFCore; public class EfTxBehavior( ILogger> logger, IDbContext dbContextBase, IPersistMessageDbContext persistMessageDbContext, IEventDispatcher eventDispatcher ) : IPipelineBehavior where TRequest : notnull, IRequest where TResponse : notnull { public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { logger.LogInformation( "{Prefix} Handled command {MediatrRequest}", nameof(EfTxBehavior), typeof(TRequest).FullName); logger.LogDebug( "{Prefix} Handled command {MediatrRequest} with content {RequestContent}", nameof(EfTxBehavior), typeof(TRequest).FullName, JsonSerializer.Serialize(request)); logger.LogInformation( "{Prefix} Open the transaction for {MediatrRequest}", nameof(EfTxBehavior), typeof(TRequest).FullName); //ref: https://learn.microsoft.com/en-us/ef/core/saving/transactions#using-systemtransactions using var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }, TransactionScopeAsyncFlowOption.Enabled); var response = await next(); logger.LogInformation( "{Prefix} Executed the {MediatrRequest} request", nameof(EfTxBehavior), typeof(TRequest).FullName); while (true) { var domainEvents = dbContextBase.GetDomainEvents(); if (domainEvents is null || !domainEvents.Any()) { return response; } await eventDispatcher.SendAsync(domainEvents.ToArray(), typeof(TRequest), cancellationToken); // Save data to database with some retry policy in distributed transaction await dbContextBase.RetryOnFailure(async () => { await dbContextBase.SaveChangesAsync(cancellationToken); }); // Save data to database with some retry policy in distributed transaction await persistMessageDbContext.RetryOnFailure(async () => { await persistMessageDbContext.SaveChangesAsync(cancellationToken); }); scope.Complete(); return response; } } }