using BuildingBlocks.EFCore; using Microsoft.EntityFrameworkCore; namespace BuildingBlocks.PersistMessageProcessor.Data; using System.Net; using Configurations; using Core.Model; using global::Polly; using Microsoft.Extensions.Logging; public class PersistMessageDbContext : DbContext, IPersistMessageDbContext { public PersistMessageDbContext(DbContextOptions options) : base(options) { } public DbSet PersistMessages => Set(); protected override void OnModelCreating(ModelBuilder builder) { builder.ApplyConfiguration(new PersistMessageConfiguration()); base.OnModelCreating(builder); builder.ToSnakeCaseTables(); } public override async Task SaveChangesAsync(CancellationToken cancellationToken = default) { OnBeforeSaving(); var policy = Policy.Handle() .WaitAndRetryAsync(retryCount: 3, sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(1), onRetry: (exception, timeSpan, retryCount, context) => { if (exception != null) { var factory = LoggerFactory.Create(b => b.AddConsole()); var logger = factory.CreateLogger(); 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)); } catch (DbUpdateConcurrencyException ex) { foreach (var entry in ex.Entries) { var currentEntity = entry.Entity; // we can use it for specific merging var databaseValues = await entry.GetDatabaseValuesAsync(cancellationToken); if (databaseValues != null) { entry.OriginalValues.SetValues(databaseValues); } } return await base.SaveChangesAsync(cancellationToken); } } private void OnBeforeSaving() { foreach (var entry in ChangeTracker.Entries()) { switch (entry.State) { case EntityState.Modified: entry.Entity.Version++; break; case EntityState.Deleted: entry.Entity.Version++; break; } } } }