diff --git a/src/BuildingBlocks/BuildingBlocks.csproj b/src/BuildingBlocks/BuildingBlocks.csproj
index 81ab599..8595b07 100644
--- a/src/BuildingBlocks/BuildingBlocks.csproj
+++ b/src/BuildingBlocks/BuildingBlocks.csproj
@@ -3,9 +3,9 @@
net7.0
enable
+ enable
-
@@ -144,7 +144,7 @@
-
+
diff --git a/src/BuildingBlocks/Core/Model/IVersion.cs b/src/BuildingBlocks/Core/Model/IVersion.cs
index 3dc2a27..e2b3138 100644
--- a/src/BuildingBlocks/Core/Model/IVersion.cs
+++ b/src/BuildingBlocks/Core/Model/IVersion.cs
@@ -1,5 +1,6 @@
namespace BuildingBlocks.Core.Model;
+// For handling optimistic concurrency
public interface IVersion
{
long Version { get; set; }
diff --git a/src/BuildingBlocks/EFCore/AppDbContextBase.cs b/src/BuildingBlocks/EFCore/AppDbContextBase.cs
index 4a2684c..9d33010 100644
--- a/src/BuildingBlocks/EFCore/AppDbContextBase.cs
+++ b/src/BuildingBlocks/EFCore/AppDbContextBase.cs
@@ -4,22 +4,24 @@ using System.Collections.Immutable;
using Core.Event;
using Core.Model;
using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.Extensions.Logging;
using Web;
using Exception = System.Exception;
using IsolationLevel = System.Data.IsolationLevel;
public abstract class AppDbContextBase : DbContext, IDbContext
{
- private readonly ICurrentUserProvider _currentUserProvider;
- private IDbContextTransaction? _currentTransaction;
+ private readonly ICurrentUserProvider? _currentUserProvider;
+ private readonly ILogger? _logger;
- protected AppDbContextBase(DbContextOptions options, ICurrentUserProvider currentUserProvider) :
+ protected AppDbContextBase(DbContextOptions options, ICurrentUserProvider? currentUserProvider = null, ILogger? logger = null) :
base(options)
{
_currentUserProvider = currentUserProvider;
+ _logger = logger;
}
+
protected override void OnModelCreating(ModelBuilder builder)
{
}
@@ -48,7 +50,29 @@ public abstract class AppDbContextBase : DbContext, IDbContext
public override async Task SaveChangesAsync(CancellationToken cancellationToken = default)
{
OnBeforeSaving();
- return await base.SaveChangesAsync(cancellationToken);
+ try
+ {
+ return await base.SaveChangesAsync(cancellationToken);
+ }
+ //ref: https://learn.microsoft.com/en-us/ef/core/saving/concurrency?tabs=data-annotations#resolving-concurrency-conflicts
+ catch (DbUpdateConcurrencyException ex)
+ {
+ foreach (var entry in ex.Entries)
+ {
+ var databaseValues = await entry.GetDatabaseValuesAsync(cancellationToken);
+
+ if (databaseValues == null)
+ {
+ _logger.LogError("The record no longer exists in the database, The record has been deleted by another user.");
+ throw;
+ }
+
+ // Refresh the original values to bypass next concurrency check
+ entry.OriginalValues.SetValues(databaseValues);
+ }
+
+ return await base.SaveChangesAsync(cancellationToken);
+ }
}
public IReadOnlyList GetDomainEvents()
diff --git a/src/BuildingBlocks/PersistMessageProcessor/Data/DesignTimeDbContextFactory.cs b/src/BuildingBlocks/PersistMessageProcessor/Data/DesignTimeDbContextFactory.cs
index ce710a6..5609afe 100644
--- a/src/BuildingBlocks/PersistMessageProcessor/Data/DesignTimeDbContextFactory.cs
+++ b/src/BuildingBlocks/PersistMessageProcessor/Data/DesignTimeDbContextFactory.cs
@@ -11,6 +11,6 @@ public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory _logger;
+ private readonly ILogger? _logger;
public PersistMessageDbContext(DbContextOptions options,
- ILogger logger)
+ ILogger? logger = null)
: base(options)
{
_logger = logger;
@@ -34,23 +32,9 @@ public class PersistMessageDbContext : DbContext, IPersistMessageDbContext
{
OnBeforeSaving();
- var policy = Policy.Handle()
- .WaitAndRetryAsync(retryCount: 3,
- sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(1),
- onRetry: (exception, timeSpan, retryCount, context) =>
- {
- if (exception != null)
- {
- _logger.LogError(exception,
- "Request failed with {StatusCode}. Waiting {TimeSpan} before next retry. Retry attempt {RetryCount}.",
- HttpStatusCode.Conflict,
- timeSpan,
- retryCount);
- }
- });
try
{
- return await policy.ExecuteAsync(async () => await base.SaveChangesAsync(cancellationToken));
+ return await base.SaveChangesAsync(cancellationToken);
}
//ref: https://learn.microsoft.com/en-us/ef/core/saving/concurrency?tabs=data-annotations#resolving-concurrency-conflicts
catch (DbUpdateConcurrencyException ex)
diff --git a/src/BuildingBlocks/PersistMessageProcessor/PersistMessageProcessor.cs b/src/BuildingBlocks/PersistMessageProcessor/PersistMessageProcessor.cs
index ff18f6e..d34d587 100644
--- a/src/BuildingBlocks/PersistMessageProcessor/PersistMessageProcessor.cs
+++ b/src/BuildingBlocks/PersistMessageProcessor/PersistMessageProcessor.cs
@@ -17,8 +17,6 @@ public class PersistMessageProcessor : IPersistMessageProcessor
private readonly IMediator _mediator;
private readonly IPersistMessageDbContext _persistMessageDbContext;
private readonly IPublishEndpoint _publishEndpoint;
- private SemaphoreSlim Semaphore => new SemaphoreSlim(1);
-
public PersistMessageProcessor(
ILogger logger,
IMediator mediator,
@@ -108,7 +106,6 @@ public class PersistMessageProcessor : IPersistMessageProcessor
}
}
-
public async Task ProcessAllAsync(CancellationToken cancellationToken = default)
{
var messages = await _persistMessageDbContext.PersistMessages
@@ -117,16 +114,7 @@ public class PersistMessageProcessor : IPersistMessageProcessor
foreach (var message in messages)
{
- await Semaphore.WaitAsync(cancellationToken);
-
- try
- {
- await ProcessAsync(message.Id, message.DeliveryType, cancellationToken);
- }
- finally
- {
- Semaphore.Release();
- }
+ await ProcessAsync(message.Id, message.DeliveryType, cancellationToken);
}
}
diff --git a/src/Services/Booking/src/Booking/Booking.csproj b/src/Services/Booking/src/Booking/Booking.csproj
index 6f0239f..22fd0ed 100644
--- a/src/Services/Booking/src/Booking/Booking.csproj
+++ b/src/Services/Booking/src/Booking/Booking.csproj
@@ -3,6 +3,7 @@
net7.0
enable
+ enable
@@ -29,11 +30,11 @@
-
+
<_Parameter1>Integration.Test
-
+
diff --git a/src/Services/Booking/tests/IntegrationTest/Integration.Test.csproj b/src/Services/Booking/tests/IntegrationTest/Integration.Test.csproj
index c21a265..6b0d8b3 100644
--- a/src/Services/Booking/tests/IntegrationTest/Integration.Test.csproj
+++ b/src/Services/Booking/tests/IntegrationTest/Integration.Test.csproj
@@ -3,6 +3,7 @@
net7.0
false
+ enable
diff --git a/src/Services/Flight/src/Flight/Data/DesignTimeDbContextFactory.cs b/src/Services/Flight/src/Flight/Data/DesignTimeDbContextFactory.cs
index 6047782..b9a7a6c 100644
--- a/src/Services/Flight/src/Flight/Data/DesignTimeDbContextFactory.cs
+++ b/src/Services/Flight/src/Flight/Data/DesignTimeDbContextFactory.cs
@@ -11,7 +11,7 @@ namespace Flight.Data
builder.UseNpgsql("Server=localhost;Port=5432;Database=flight;User Id=postgres;Password=postgres;Include Error Detail=true")
.UseSnakeCaseNamingConvention();
- return new FlightDbContext(builder.Options, null);
+ return new FlightDbContext(builder.Options);
}
}
}
diff --git a/src/Services/Flight/src/Flight/Data/FlightDbContext.cs b/src/Services/Flight/src/Flight/Data/FlightDbContext.cs
index b1945e3..8f9f77c 100644
--- a/src/Services/Flight/src/Flight/Data/FlightDbContext.cs
+++ b/src/Services/Flight/src/Flight/Data/FlightDbContext.cs
@@ -7,11 +7,13 @@ using Microsoft.EntityFrameworkCore;
namespace Flight.Data;
using BuildingBlocks.Web;
+using Microsoft.Extensions.Logging;
public sealed class FlightDbContext : AppDbContextBase
{
- public FlightDbContext(DbContextOptions options, ICurrentUserProvider currentUserProvider) : base(
- options, currentUserProvider)
+ public FlightDbContext(DbContextOptions options, ICurrentUserProvider? currentUserProvider = null,
+ ILogger? logger = null) : base(
+ options, currentUserProvider, logger)
{
}
diff --git a/src/Services/Flight/src/Flight/Flight.csproj b/src/Services/Flight/src/Flight/Flight.csproj
index b8d25fe..5ec1a9a 100644
--- a/src/Services/Flight/src/Flight/Flight.csproj
+++ b/src/Services/Flight/src/Flight/Flight.csproj
@@ -2,6 +2,7 @@
net7.0
+ enable
diff --git a/src/Services/Flight/tests/EndToEndTest/EndToEnd.Test.csproj b/src/Services/Flight/tests/EndToEndTest/EndToEnd.Test.csproj
index 5d4348e..6acf245 100644
--- a/src/Services/Flight/tests/EndToEndTest/EndToEnd.Test.csproj
+++ b/src/Services/Flight/tests/EndToEndTest/EndToEnd.Test.csproj
@@ -4,7 +4,6 @@
net7.0
enable
enable
-
false
diff --git a/src/Services/Flight/tests/IntegrationTest/Integration.Test.csproj b/src/Services/Flight/tests/IntegrationTest/Integration.Test.csproj
index 1807978..ed55732 100644
--- a/src/Services/Flight/tests/IntegrationTest/Integration.Test.csproj
+++ b/src/Services/Flight/tests/IntegrationTest/Integration.Test.csproj
@@ -3,6 +3,7 @@
net7.0
false
+ enable
diff --git a/src/Services/Flight/tests/UnitTest/Common/DbContextFactory.cs b/src/Services/Flight/tests/UnitTest/Common/DbContextFactory.cs
index 62c5bcb..a00335c 100644
--- a/src/Services/Flight/tests/UnitTest/Common/DbContextFactory.cs
+++ b/src/Services/Flight/tests/UnitTest/Common/DbContextFactory.cs
@@ -24,7 +24,7 @@ public static class DbContextFactory
var options = new DbContextOptionsBuilder()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()).Options;
- var context = new FlightDbContext(options, currentUserProvider: null);
+ var context = new FlightDbContext(options, currentUserProvider: null, null);
// Seed our data
FlightDataSeeder(context);
diff --git a/src/Services/Flight/tests/UnitTest/Unit.Test.csproj b/src/Services/Flight/tests/UnitTest/Unit.Test.csproj
index 7ebb982..d69eeb2 100644
--- a/src/Services/Flight/tests/UnitTest/Unit.Test.csproj
+++ b/src/Services/Flight/tests/UnitTest/Unit.Test.csproj
@@ -3,6 +3,7 @@
net7.0
false
+ enable
diff --git a/src/Services/Identity/src/Identity/Data/IdentityContext.cs b/src/Services/Identity/src/Identity/Data/IdentityContext.cs
index ff67c9a..2195862 100644
--- a/src/Services/Identity/src/Identity/Data/IdentityContext.cs
+++ b/src/Services/Identity/src/Identity/Data/IdentityContext.cs
@@ -16,14 +16,16 @@ namespace Identity.Data;
using System;
using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.Extensions.Logging;
public sealed class IdentityContext : IdentityDbContext, IDbContext
{
- private IDbContextTransaction? _currentTransaction;
+ private readonly ILogger? _logger;
- public IdentityContext(DbContextOptions options) : base(options)
+ public IdentityContext(DbContextOptions options, ILogger? logger = null) : base(options)
{
+ _logger = logger;
}
protected override void OnModelCreating(ModelBuilder builder)
@@ -58,7 +60,29 @@ public sealed class IdentityContext : IdentityDbContext SaveChangesAsync(CancellationToken cancellationToken = default)
{
OnBeforeSaving();
- return await base.SaveChangesAsync(cancellationToken);
+ try
+ {
+ return await base.SaveChangesAsync(cancellationToken);
+ }
+ //ref: https://learn.microsoft.com/en-us/ef/core/saving/concurrency?tabs=data-annotations#resolving-concurrency-conflicts
+ catch (DbUpdateConcurrencyException ex)
+ {
+ foreach (var entry in ex.Entries)
+ {
+ var databaseValues = await entry.GetDatabaseValuesAsync(cancellationToken);
+
+ if (databaseValues == null)
+ {
+ _logger.LogError("The record no longer exists in the database, The record has been deleted by another user.");
+ throw;
+ }
+
+ // Refresh the original values to bypass next concurrency check
+ entry.OriginalValues.SetValues(databaseValues);
+ }
+
+ return await base.SaveChangesAsync(cancellationToken);
+ }
}
public IReadOnlyList GetDomainEvents()
diff --git a/src/Services/Identity/src/Identity/Identity.csproj b/src/Services/Identity/src/Identity/Identity.csproj
index 26628df..cf433e1 100644
--- a/src/Services/Identity/src/Identity/Identity.csproj
+++ b/src/Services/Identity/src/Identity/Identity.csproj
@@ -2,6 +2,7 @@
net7.0
+ enable
diff --git a/src/Services/Identity/tests/IntegrationTest/Integration.Test.csproj b/src/Services/Identity/tests/IntegrationTest/Integration.Test.csproj
index 5cefc2e..7153e37 100644
--- a/src/Services/Identity/tests/IntegrationTest/Integration.Test.csproj
+++ b/src/Services/Identity/tests/IntegrationTest/Integration.Test.csproj
@@ -3,6 +3,7 @@
net7.0
false
+ enable
diff --git a/src/Services/Passenger/src/Passenger.Api/Passenger.Api.csproj b/src/Services/Passenger/src/Passenger.Api/Passenger.Api.csproj
index 91eafd6..f18f014 100644
--- a/src/Services/Passenger/src/Passenger.Api/Passenger.Api.csproj
+++ b/src/Services/Passenger/src/Passenger.Api/Passenger.Api.csproj
@@ -3,6 +3,7 @@
net7.0
enable
+ enable
diff --git a/src/Services/Passenger/src/Passenger/Data/DesignTimeDbContextFactory.cs b/src/Services/Passenger/src/Passenger/Data/DesignTimeDbContextFactory.cs
index 87860fb..d56404d 100644
--- a/src/Services/Passenger/src/Passenger/Data/DesignTimeDbContextFactory.cs
+++ b/src/Services/Passenger/src/Passenger/Data/DesignTimeDbContextFactory.cs
@@ -11,6 +11,6 @@ public class DesignTimeDbContextFactory: IDesignTimeDbContextFactory options, ICurrentUserProvider currentUserProvider) :
- base(options, currentUserProvider)
+ public PassengerDbContext(DbContextOptions options,
+ ICurrentUserProvider? currentUserProvider = null, ILogger? logger = null) :
+ base(options, currentUserProvider, logger)
{
}
diff --git a/src/Services/Passenger/src/Passenger/Passenger.csproj b/src/Services/Passenger/src/Passenger/Passenger.csproj
index afe0565..40cf04a 100644
--- a/src/Services/Passenger/src/Passenger/Passenger.csproj
+++ b/src/Services/Passenger/src/Passenger/Passenger.csproj
@@ -3,6 +3,7 @@
net7.0
enable
+ enable
diff --git a/src/Services/Passenger/tests/IntegrationTest/Integration.Test.csproj b/src/Services/Passenger/tests/IntegrationTest/Integration.Test.csproj
index f96ccb9..686c844 100644
--- a/src/Services/Passenger/tests/IntegrationTest/Integration.Test.csproj
+++ b/src/Services/Passenger/tests/IntegrationTest/Integration.Test.csproj
@@ -3,6 +3,7 @@
net7.0
false
+ enable