mirror of
https://github.com/meysamhadeli/booking-microservices.git
synced 2026-04-11 02:20:20 +08:00
129 lines
4.1 KiB
C#
129 lines
4.1 KiB
C#
using BuildingBlocks.EFCore;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace BuildingBlocks.PersistMessageProcessor.Data;
|
|
|
|
using Configurations;
|
|
using Core.Model;
|
|
using Microsoft.Extensions.Logging;
|
|
using Exception = System.Exception;
|
|
using IsolationLevel = System.Data.IsolationLevel;
|
|
|
|
public class PersistMessageDbContext : DbContext, IPersistMessageDbContext
|
|
{
|
|
private readonly ILogger<PersistMessageDbContext>? _logger;
|
|
|
|
public PersistMessageDbContext(DbContextOptions<PersistMessageDbContext> options,
|
|
ILogger<PersistMessageDbContext>? logger = null)
|
|
: base(options)
|
|
{
|
|
_logger = logger;
|
|
}
|
|
|
|
public DbSet<PersistMessage> PersistMessages => Set<PersistMessage>();
|
|
|
|
protected override void OnModelCreating(ModelBuilder builder)
|
|
{
|
|
builder.ApplyConfiguration(new PersistMessageConfiguration());
|
|
base.OnModelCreating(builder);
|
|
builder.ToSnakeCaseTables();
|
|
}
|
|
|
|
//ref: https://learn.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency#execution-strategies-and-transactions
|
|
public Task ExecuteTransactionalAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
var strategy = Database.CreateExecutionStrategy();
|
|
return strategy.ExecuteAsync(async () =>
|
|
{
|
|
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<int> SaveChangesAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
OnBeforeSaving();
|
|
|
|
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 void CreatePersistMessageTable()
|
|
{
|
|
if (Database.GetPendingMigrations().Any())
|
|
{
|
|
throw new InvalidOperationException("Cannot create table if there are pending migrations.");
|
|
}
|
|
|
|
string createTableSql = @"
|
|
create table if not exists persist_message (
|
|
id uuid not null,
|
|
data_type text,
|
|
data text,
|
|
created timestamp with time zone not null,
|
|
retry_count integer not null,
|
|
message_status text not null default 'InProgress'::text,
|
|
delivery_type text not null default 'Outbox'::text,
|
|
version bigint not null,
|
|
constraint pk_persist_message primary key (id)
|
|
)";
|
|
|
|
Database.ExecuteSqlRaw(createTableSql);
|
|
}
|
|
|
|
private void OnBeforeSaving()
|
|
{
|
|
try
|
|
{
|
|
foreach (var entry in ChangeTracker.Entries<IVersion>())
|
|
{
|
|
switch (entry.State)
|
|
{
|
|
case EntityState.Modified:
|
|
entry.Entity.Version++;
|
|
break;
|
|
|
|
case EntityState.Deleted:
|
|
entry.Entity.Version++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new Exception("try for find IVersion", ex);
|
|
}
|
|
}
|
|
}
|