From f42f29db03c8912dddba7a939b9313e6c350b624 Mon Sep 17 00:00:00 2001 From: Miguel Veloso Date: Thu, 21 Feb 2019 17:19:31 +0000 Subject: [PATCH] Ensure transaction is committed in the correct context, when handling chained or nested commands --- .../Behaviors/TransactionBehaviour.cs | 15 +++++++++------ .../OrderingContext.cs | 19 +++++++++++++------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs b/src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs index 27da201fc..da3c2640c 100644 --- a/src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs +++ b/src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs @@ -27,26 +27,30 @@ namespace Ordering.API.Application.Behaviors public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) { - TResponse response = default(TResponse); + var response = default(TResponse); var typeName = request.GetGenericTypeName(); try { + if (_dbContext.HasActiveTransaction) + { + return await next(); + } + var strategy = _dbContext.Database.CreateExecutionStrategy(); await strategy.ExecuteAsync(async () => { - var transaction = await _dbContext.BeginTransactionAsync(); - + using (var transaction = await _dbContext.BeginTransactionAsync()) using (LogContext.PushProperty("TransactionContext", transaction.TransactionId)) { _logger.LogInformation("----- Begin transaction {TransactionId} for {CommandName} ({@Command})", transaction.TransactionId, typeName, request); response = await next(); - await _dbContext.CommitTransactionAsync(); + _logger.LogInformation("----- Commit transaction {TransactionId} for {CommandName}", transaction.TransactionId, typeName); - _logger.LogInformation("----- Transaction {TransactionId} committed for {CommandName}", transaction.TransactionId, typeName); + await _dbContext.CommitTransactionAsync(transaction); } await _orderingIntegrationEventService.PublishEventsThroughEventBusAsync(); @@ -58,7 +62,6 @@ namespace Ordering.API.Application.Behaviors { _logger.LogError(ex, "----- ERROR Handling transaction for {CommandName} ({@Command})", typeName, request); - _dbContext.RollbackTransaction(); throw; } } diff --git a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs index 766cecd91..18e72fe29 100644 --- a/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs +++ b/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs @@ -27,10 +27,12 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure private readonly IMediator _mediator; private IDbContextTransaction _currentTransaction; - private OrderingContext(DbContextOptions options) : base (options) { } + private OrderingContext(DbContextOptions options) : base(options) { } public IDbContextTransaction GetCurrentTransaction => _currentTransaction; + public bool HasActiveTransaction => _currentTransaction != null; + public OrderingContext(DbContextOptions options, IMediator mediator) : base(options) { _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); @@ -47,7 +49,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure modelBuilder.ApplyConfiguration(new OrderItemEntityTypeConfiguration()); modelBuilder.ApplyConfiguration(new CardTypeEntityTypeConfiguration()); modelBuilder.ApplyConfiguration(new OrderStatusEntityTypeConfiguration()); - modelBuilder.ApplyConfiguration(new BuyerEntityTypeConfiguration()); + modelBuilder.ApplyConfiguration(new BuyerEntityTypeConfiguration()); } public async Task SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken)) @@ -69,17 +71,22 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure public async Task BeginTransactionAsync() { - _currentTransaction = _currentTransaction ?? await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted); + if (_currentTransaction != null) return null; + + _currentTransaction = await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted); return _currentTransaction; } - public async Task CommitTransactionAsync() + public async Task CommitTransactionAsync(IDbContextTransaction transaction) { + if (transaction == null) throw new ArgumentNullException(nameof(transaction)); + if (transaction != _currentTransaction) throw new InvalidOperationException($"Transaction {transaction.TransactionId} is not current"); + try { await SaveChangesAsync(); - _currentTransaction?.Commit(); + transaction.Commit(); } catch { @@ -120,7 +127,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure var optionsBuilder = new DbContextOptionsBuilder() .UseSqlServer("Server=.;Initial Catalog=Microsoft.eShopOnContainers.Services.OrderingDb;Integrated Security=true"); - return new OrderingContext(optionsBuilder.Options,new NoMediator()); + return new OrderingContext(optionsBuilder.Options, new NoMediator()); } class NoMediator : IMediator