using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services; namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Services; /// Owns the SQL transaction spanning configuration saves and the final audit flush. public sealed class ConfigurationCommandTransaction(IServiceScopeFactory scopes) : IConfigurationCommandTransaction { /// public async Task ExecuteAsync(Func> operation) { using var strategyScope = scopes.CreateScope(); var strategy = strategyScope.ServiceProvider.GetRequiredService().Database.CreateExecutionStrategy(); var commitStarted = false; var result = await strategy.ExecuteAsync(async () => { // A lost commit acknowledgment must never automatically replay a create. if (commitStarted) throw new InvalidOperationException("Configuration commit outcome is uncertain; inspect the configuration audit before retrying."); await using var attempt = scopes.CreateAsyncScope(); var db = attempt.ServiceProvider.GetRequiredService(); await using var transaction = await db.Database.BeginTransactionAsync(); var value = await operation(attempt.ServiceProvider); await db.SaveChangesAsync(); // AuditService stages rows; flush them inside this transaction. commitStarted = true; await transaction.CommitAsync(); await attempt.ServiceProvider.GetRequiredService().RunAsync(); return value; }); return result; } } /// Per-attempt postcommit actions; rolled-back attempts never run them. public sealed class ConfigurationCommitActions(ILogger logger) : IConfigurationCommitActions { private readonly List> _actions = []; /// public void Add(Func action) => _actions.Add(action); /// Runs every registered post-commit action, in registration order. /// A task that represents the asynchronous operation. internal async Task RunAsync() { foreach (var action in _actions) { try { await action(); } catch (Exception ex) { logger.LogError(ex, "Configuration committed, but local cache maintenance failed"); } } } } /// Refreshes circuit-held read entities after a command owned by another scope. public sealed class ConfigurationReadSession(ScadaBridgeDbContext db) : IConfigurationReadSession { /// public void Reset() => db.ChangeTracker.Clear(); }