diff --git a/src/BuildingBlocks/EFCore/AppDbContextBase.cs b/src/BuildingBlocks/EFCore/AppDbContextBase.cs index 437a589..b826f3c 100644 --- a/src/BuildingBlocks/EFCore/AppDbContextBase.cs +++ b/src/BuildingBlocks/EFCore/AppDbContextBase.cs @@ -6,66 +6,41 @@ using BuildingBlocks.Core.Model; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage; using System.Data; -using System.Security.Claims; -using Microsoft.AspNetCore.Http; +using Web; using Exception = System.Exception; public abstract class AppDbContextBase : DbContext, IDbContext { - private readonly IHttpContextAccessor _httpContextAccessor; - private IDbContextTransaction _currentTransaction; + private readonly ICurrentUserProvider _currentUserProvider; - protected AppDbContextBase(DbContextOptions options, IHttpContextAccessor httpContextAccessor = default) : + protected AppDbContextBase(DbContextOptions options, ICurrentUserProvider currentUserProvider) : base(options) { - _httpContextAccessor = httpContextAccessor; + _currentUserProvider = currentUserProvider; } protected override void OnModelCreating(ModelBuilder builder) { - // ref: https://github.com/pdevito3/MessageBusTestingInMemHarness/blob/main/RecipeManagement/src/RecipeManagement/Databases/RecipesDbContext.cs } - public async Task BeginTransactionAsync(CancellationToken cancellationToken = default) + //ref: https://learn.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency#execution-strategies-and-transactions + public Task ExecuteTransactionalAsync(CancellationToken cancellationToken = default) { - if (_currentTransaction != null) + var strategy = Database.CreateExecutionStrategy(); + return strategy.ExecuteAsync(async () => { - return; - } - - _currentTransaction ??= await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted, cancellationToken); - } - - public async Task CommitTransactionAsync(CancellationToken cancellationToken = default) - { - try - { - await SaveChangesAsync(cancellationToken); - await _currentTransaction?.CommitAsync(cancellationToken)!; - } - catch - { - await RollbackTransactionAsync(cancellationToken); - throw; - } - finally - { - _currentTransaction?.Dispose(); - _currentTransaction = null; - } - } - - public async Task RollbackTransactionAsync(CancellationToken cancellationToken = default) - { - try - { - await _currentTransaction?.RollbackAsync(cancellationToken)!; - } - finally - { - _currentTransaction?.Dispose(); - _currentTransaction = null; - } + await using var transaction = await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted, cancellationToken); + try + { + await SaveChangesAsync(cancellationToken); + await transaction.CommitAsync(cancellationToken); + } + catch + { + await transaction.RollbackAsync(cancellationToken); + throw; + } + }); } public override async Task SaveChangesAsync(CancellationToken cancellationToken = default) @@ -100,7 +75,7 @@ public abstract class AppDbContextBase : DbContext, IDbContext foreach (var entry in ChangeTracker.Entries()) { var isAuditable = entry.Entity.GetType().IsAssignableTo(typeof(IAggregate)); - var userId = GetCurrentUserId(); + var userId = _currentUserProvider.GetCurrentUserId(); if (isAuditable) { @@ -133,13 +108,4 @@ public abstract class AppDbContextBase : DbContext, IDbContext throw new Exception("try for find IAggregate", ex); } } - - private long? GetCurrentUserId() - { - var nameIdentifier = _httpContextAccessor?.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier); - - long.TryParse(nameIdentifier, out var userId); - - return userId; - } } diff --git a/src/BuildingBlocks/EFCore/EfTxBehavior.cs b/src/BuildingBlocks/EFCore/EfTxBehavior.cs index e156fec..393fb5d 100644 --- a/src/BuildingBlocks/EFCore/EfTxBehavior.cs +++ b/src/BuildingBlocks/EFCore/EfTxBehavior.cs @@ -42,29 +42,20 @@ public class EfTxBehavior : IPipelineBehavior), typeof(TRequest).FullName); - await _dbContextBase.BeginTransactionAsync(cancellationToken); - try - { - var response = await next(); + var response = await next(); - _logger.LogInformation( - "{Prefix} Executed the {MediatrRequest} request", - nameof(EfTxBehavior), - typeof(TRequest).FullName); + _logger.LogInformation( + "{Prefix} Executed the {MediatrRequest} request", + nameof(EfTxBehavior), + typeof(TRequest).FullName); - var domainEvents = _dbContextBase.GetDomainEvents(); + var domainEvents = _dbContextBase.GetDomainEvents(); - await _eventDispatcher.SendAsync(domainEvents.ToArray(), typeof(TRequest), cancellationToken); + await _eventDispatcher.SendAsync(domainEvents.ToArray(), typeof(TRequest), cancellationToken); - await _dbContextBase.CommitTransactionAsync(cancellationToken); + await _dbContextBase.ExecuteTransactionalAsync(cancellationToken); - return response; - } - catch (System.Exception ex) - { - await _dbContextBase.RollbackTransactionAsync(cancellationToken); - throw; - } + return response; } } diff --git a/src/BuildingBlocks/EFCore/Extensions.cs b/src/BuildingBlocks/EFCore/Extensions.cs index 45d76f3..e6d74ba 100644 --- a/src/BuildingBlocks/EFCore/Extensions.cs +++ b/src/BuildingBlocks/EFCore/Extensions.cs @@ -35,6 +35,8 @@ public static class Extensions dbOptions => { dbOptions.MigrationsAssembly(typeof(TContext).Assembly.GetName().Name); + //ref: https://learn.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency + dbOptions.EnableRetryOnFailure(3, TimeSpan.FromSeconds(1), null); }) // https://github.com/efcore/EFCore.NamingConventions .UseSnakeCaseNamingConvention(); diff --git a/src/BuildingBlocks/EFCore/IDbContext.cs b/src/BuildingBlocks/EFCore/IDbContext.cs index af53ad3..48ac238 100644 --- a/src/BuildingBlocks/EFCore/IDbContext.cs +++ b/src/BuildingBlocks/EFCore/IDbContext.cs @@ -7,8 +7,6 @@ public interface IDbContext { DbSet Set() where TEntity : class; IReadOnlyList GetDomainEvents(); - Task BeginTransactionAsync(CancellationToken cancellationToken = default); - Task CommitTransactionAsync(CancellationToken cancellationToken = default); - Task RollbackTransactionAsync(CancellationToken cancellationToken = default); + Task ExecuteTransactionalAsync(CancellationToken cancellationToken = default); Task SaveChangesAsync(CancellationToken cancellationToken = default); } diff --git a/src/BuildingBlocks/PersistMessageProcessor/Data/PersistMessageDbContext.cs b/src/BuildingBlocks/PersistMessageProcessor/Data/PersistMessageDbContext.cs index 73e1f2d..c5058e5 100644 --- a/src/BuildingBlocks/PersistMessageProcessor/Data/PersistMessageDbContext.cs +++ b/src/BuildingBlocks/PersistMessageProcessor/Data/PersistMessageDbContext.cs @@ -16,7 +16,7 @@ public class PersistMessageDbContext : DbContext, IPersistMessageDbContext { } - public DbSet PersistMessages { get; set; } + public DbSet PersistMessages => Set(); protected override void OnModelCreating(ModelBuilder builder) { @@ -48,13 +48,13 @@ public class PersistMessageDbContext : DbContext, IPersistMessageDbContext }); try { - await policy.ExecuteAsync(async () => await base.SaveChangesAsync(cancellationToken)); + return await policy.ExecuteAsync(async () => await base.SaveChangesAsync(cancellationToken)); } catch (DbUpdateConcurrencyException ex) { foreach (var entry in ex.Entries) { - var currentEntity = entry.Entity; + var currentEntity = entry.Entity; // we can use it for specific merging var databaseValues = await entry.GetDatabaseValuesAsync(cancellationToken); if (databaseValues != null) @@ -65,8 +65,6 @@ public class PersistMessageDbContext : DbContext, IPersistMessageDbContext return await base.SaveChangesAsync(cancellationToken); } - - return 0; } private void OnBeforeSaving() diff --git a/src/BuildingBlocks/PersistMessageProcessor/IPersistMessageDbContext.cs b/src/BuildingBlocks/PersistMessageProcessor/IPersistMessageDbContext.cs index 0eee375..e74d944 100644 --- a/src/BuildingBlocks/PersistMessageProcessor/IPersistMessageDbContext.cs +++ b/src/BuildingBlocks/PersistMessageProcessor/IPersistMessageDbContext.cs @@ -2,10 +2,8 @@ namespace BuildingBlocks.PersistMessageProcessor; -using EFCore; - public interface IPersistMessageDbContext { - DbSet PersistMessages { get; set; } + DbSet PersistMessages { get; } Task SaveChangesAsync(CancellationToken cancellationToken = default); } diff --git a/src/BuildingBlocks/PersistMessageProcessor/PersistMessageProcessor.cs b/src/BuildingBlocks/PersistMessageProcessor/PersistMessageProcessor.cs index f1815a6..8dc5c6e 100644 --- a/src/BuildingBlocks/PersistMessageProcessor/PersistMessageProcessor.cs +++ b/src/BuildingBlocks/PersistMessageProcessor/PersistMessageProcessor.cs @@ -6,11 +6,12 @@ using BuildingBlocks.IdsGenerator; using BuildingBlocks.Utils; using MassTransit; using MediatR; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace BuildingBlocks.PersistMessageProcessor; +using Microsoft.EntityFrameworkCore; + public class PersistMessageProcessor : IPersistMessageProcessor { private readonly ILogger _logger; diff --git a/src/Services/Flight/src/Flight/Data/FlightDbContext.cs b/src/Services/Flight/src/Flight/Data/FlightDbContext.cs index a13cabe..96a18d9 100644 --- a/src/Services/Flight/src/Flight/Data/FlightDbContext.cs +++ b/src/Services/Flight/src/Flight/Data/FlightDbContext.cs @@ -6,12 +6,12 @@ using Microsoft.EntityFrameworkCore; namespace Flight.Data; -using Microsoft.AspNetCore.Http; +using BuildingBlocks.Web; public sealed class FlightDbContext : AppDbContextBase { - public FlightDbContext(DbContextOptions options, IHttpContextAccessor httpContextAccessor = default) : base( - options, httpContextAccessor) + public FlightDbContext(DbContextOptions options, ICurrentUserProvider currentUserProvider = default) : base( + options, currentUserProvider) { } @@ -23,8 +23,8 @@ public sealed class FlightDbContext : AppDbContextBase protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); - builder.FilterSoftDeletedProperties(); builder.ApplyConfigurationsFromAssembly(typeof(FlightRoot).Assembly); + builder.FilterSoftDeletedProperties(); builder.ToSnakeCaseTables(); } } diff --git a/src/Services/Identity/src/Identity/Data/IdentityContext.cs b/src/Services/Identity/src/Identity/Data/IdentityContext.cs index f819f9a..93f6afc 100644 --- a/src/Services/Identity/src/Identity/Data/IdentityContext.cs +++ b/src/Services/Identity/src/Identity/Data/IdentityContext.cs @@ -34,54 +34,32 @@ public sealed class IdentityContext : IdentityDbContext + { + await using var transaction = await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted, cancellationToken); + try + { + await SaveChangesAsync(cancellationToken); + await transaction.CommitAsync(cancellationToken); + } + catch + { + await transaction.RollbackAsync(cancellationToken); + throw; + } + }); + } + public override async Task SaveChangesAsync(CancellationToken cancellationToken = default) { OnBeforeSaving(); return await base.SaveChangesAsync(cancellationToken); } - public async Task BeginTransactionAsync(CancellationToken cancellationToken = default) - { - if (_currentTransaction != null) - { - return; - } - - _currentTransaction = await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted, cancellationToken); - } - - public async Task CommitTransactionAsync(CancellationToken cancellationToken = default) - { - try - { - await SaveChangesAsync(cancellationToken); - await _currentTransaction?.CommitAsync(cancellationToken)!; - } - catch - { - await RollbackTransactionAsync(cancellationToken); - throw; - } - finally - { - _currentTransaction?.Dispose(); - _currentTransaction = null; - } - } - - public async Task RollbackTransactionAsync(CancellationToken cancellationToken = default) - { - try - { - await _currentTransaction?.RollbackAsync(cancellationToken)!; - } - finally - { - _currentTransaction?.Dispose(); - _currentTransaction = null; - } - } - public IReadOnlyList GetDomainEvents() { var domainEntities = ChangeTracker diff --git a/src/Services/Passenger/src/Passenger/Data/PassengerDbContext.cs b/src/Services/Passenger/src/Passenger/Data/PassengerDbContext.cs index 53d534b..c33ba84 100644 --- a/src/Services/Passenger/src/Passenger/Data/PassengerDbContext.cs +++ b/src/Services/Passenger/src/Passenger/Data/PassengerDbContext.cs @@ -1,16 +1,15 @@ using System.Reflection; using BuildingBlocks.EFCore; using Microsoft.EntityFrameworkCore; +using BuildingBlocks.Web; namespace Passenger.Data; -using Microsoft.AspNetCore.Http; - public sealed class PassengerDbContext : AppDbContextBase { public PassengerDbContext(DbContextOptions options, - IHttpContextAccessor httpContextAccessor = default) : - base(options, httpContextAccessor) + ICurrentUserProvider currentUserProvider = default) : + base(options, currentUserProvider) { } @@ -20,6 +19,7 @@ public sealed class PassengerDbContext : AppDbContextBase { builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); base.OnModelCreating(builder); + builder.FilterSoftDeletedProperties(); builder.ToSnakeCaseTables(); } }