Browse Source

Ensure transaction is committed in the correct context, when handling chained or nested commands

pull/952/head
Miguel Veloso 6 years ago
parent
commit
f42f29db03
2 changed files with 22 additions and 12 deletions
  1. +9
    -6
      src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs
  2. +13
    -6
      src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs

+ 9
- 6
src/Services/Ordering/Ordering.API/Application/Behaviors/TransactionBehaviour.cs View File

@ -27,26 +27,30 @@ namespace Ordering.API.Application.Behaviors
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next) public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{ {
TResponse response = default(TResponse);
var response = default(TResponse);
var typeName = request.GetGenericTypeName(); var typeName = request.GetGenericTypeName();
try try
{ {
if (_dbContext.HasActiveTransaction)
{
return await next();
}
var strategy = _dbContext.Database.CreateExecutionStrategy(); var strategy = _dbContext.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () => await strategy.ExecuteAsync(async () =>
{ {
var transaction = await _dbContext.BeginTransactionAsync();
using (var transaction = await _dbContext.BeginTransactionAsync())
using (LogContext.PushProperty("TransactionContext", transaction.TransactionId)) using (LogContext.PushProperty("TransactionContext", transaction.TransactionId))
{ {
_logger.LogInformation("----- Begin transaction {TransactionId} for {CommandName} ({@Command})", transaction.TransactionId, typeName, request); _logger.LogInformation("----- Begin transaction {TransactionId} for {CommandName} ({@Command})", transaction.TransactionId, typeName, request);
response = await next(); 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(); await _orderingIntegrationEventService.PublishEventsThroughEventBusAsync();
@ -58,7 +62,6 @@ namespace Ordering.API.Application.Behaviors
{ {
_logger.LogError(ex, "----- ERROR Handling transaction for {CommandName} ({@Command})", typeName, request); _logger.LogError(ex, "----- ERROR Handling transaction for {CommandName} ({@Command})", typeName, request);
_dbContext.RollbackTransaction();
throw; throw;
} }
} }


+ 13
- 6
src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs View File

@ -27,10 +27,12 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
private readonly IMediator _mediator; private readonly IMediator _mediator;
private IDbContextTransaction _currentTransaction; private IDbContextTransaction _currentTransaction;
private OrderingContext(DbContextOptions<OrderingContext> options) : base (options) { }
private OrderingContext(DbContextOptions<OrderingContext> options) : base(options) { }
public IDbContextTransaction GetCurrentTransaction => _currentTransaction; public IDbContextTransaction GetCurrentTransaction => _currentTransaction;
public bool HasActiveTransaction => _currentTransaction != null;
public OrderingContext(DbContextOptions<OrderingContext> options, IMediator mediator) : base(options) public OrderingContext(DbContextOptions<OrderingContext> options, IMediator mediator) : base(options)
{ {
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator)); _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
@ -47,7 +49,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
modelBuilder.ApplyConfiguration(new OrderItemEntityTypeConfiguration()); modelBuilder.ApplyConfiguration(new OrderItemEntityTypeConfiguration());
modelBuilder.ApplyConfiguration(new CardTypeEntityTypeConfiguration()); modelBuilder.ApplyConfiguration(new CardTypeEntityTypeConfiguration());
modelBuilder.ApplyConfiguration(new OrderStatusEntityTypeConfiguration()); modelBuilder.ApplyConfiguration(new OrderStatusEntityTypeConfiguration());
modelBuilder.ApplyConfiguration(new BuyerEntityTypeConfiguration());
modelBuilder.ApplyConfiguration(new BuyerEntityTypeConfiguration());
} }
public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken)) public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken))
@ -69,17 +71,22 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
public async Task<IDbContextTransaction> BeginTransactionAsync() public async Task<IDbContextTransaction> BeginTransactionAsync()
{ {
_currentTransaction = _currentTransaction ?? await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted);
if (_currentTransaction != null) return null;
_currentTransaction = await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted);
return _currentTransaction; 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 try
{ {
await SaveChangesAsync(); await SaveChangesAsync();
_currentTransaction?.Commit();
transaction.Commit();
} }
catch catch
{ {
@ -120,7 +127,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
var optionsBuilder = new DbContextOptionsBuilder<OrderingContext>() var optionsBuilder = new DbContextOptionsBuilder<OrderingContext>()
.UseSqlServer("Server=.;Initial Catalog=Microsoft.eShopOnContainers.Services.OrderingDb;Integrated Security=true"); .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 class NoMediator : IMediator


Loading…
Cancel
Save