using System.Collections.Immutable; using HermesSocketLibrary.db; namespace HermesSocketServer.Store.Internal { public abstract class ComplexAutoSavedStore : GroupSaveStore where K : class where V : class { private readonly IList _removedValues; private readonly GroupSaveSqlGenerator _generator; private readonly DatabaseTable _table; private readonly Database _database; private readonly Serilog.ILogger _logger; public ComplexAutoSavedStore(DatabaseTable table, Database database, Serilog.ILogger logger) : base() { if (table.KeyColumns.Length <= 1) throw new InvalidOperationException($"Use AutoSavedStore instead due to a single key column [table name: {table.TableName}]"); _removedValues = new List(); if (table.TypeMapping == null) _generator = new GroupSaveSqlGenerator(table.PropertyMapping, logger); else _generator = new GroupSaveSqlGenerator(table.PropertyMapping, table.TypeMapping, logger); _table = table; _database = database; _logger = logger; } public override async Task Save() { var allColumns = _table.KeyColumns.Union(_table.DataColumns).ToArray(); var typeMapping = _table.TypeMapping ?? new Dictionary(); await GenerateQuery(_added, (size) => _generator.GeneratePreparedInsertSql(_table.TableName, size, allColumns, typeMapping), async (query, _, values) => await _generator.DoPreparedStatement(_database, query, values, allColumns)); await GenerateQuery(_modified, (size) => _generator.GeneratePreparedUpdateSql(_table.TableName, size, _table.KeyColumns, _table.DataColumns, typeMapping), async (query, _, values) => await _generator.DoPreparedStatement(_database, query, values, allColumns)); await GenerateDeleteQuery(_deleted, _removedValues, (size) => _generator.GeneratePreparedDeleteSql(_table.TableName, size, _table.KeyColumns, typeMapping), async (query, list) => await _generator.DoPreparedStatement(_database, query, list, _table.KeyColumns)); } private async Task GenerateQuery(IList keys, Func generate, Func, IEnumerable, Task> execute) { ImmutableList? list = null; _rwls.EnterUpgradeableReadLock(); try { if (!keys.Any()) return; _rwls.EnterWriteLock(); try { list = keys.ToImmutableList(); keys.Clear(); } finally { _rwls.ExitWriteLock(); } } finally { _rwls.ExitUpgradeableReadLock(); } var query = generate(list.Count); var values = list.Select(id => _store[id]).Where(v => v != null); int rowsAffected = await execute(query, list, values); if (rowsAffected != list.Count) { _logger.Error($"Rows affected in database ({rowsAffected}) and the number of items that should be modified ({list.Count}) do not match: {query}"); } else { _logger.Information($"{rowsAffected} rows were affected by this query: {query}"); } } private async Task GenerateDeleteQuery(IList keys, IList values, Func generate, Func, Task> execute) { ImmutableList? list = null; _rwls.EnterUpgradeableReadLock(); try { if (!keys.Any() || !values.Any()) return; _rwls.EnterWriteLock(); try { list = values.ToImmutableList(); values.Clear(); keys.Clear(); } finally { _rwls.ExitWriteLock(); } } finally { _rwls.ExitUpgradeableReadLock(); } var query = generate(list.Count); int rowsAffected = await execute(query, list); if (rowsAffected != list.Count) { _logger.Error($"Rows affected in database ({rowsAffected}) and the number of items that should be modified ({list.Count}) do not match: {query}"); } else { _logger.Information($"{rowsAffected} rows were affected by this query: {query}"); } } protected override void OnPostRemove(K key, V value) => _removedValues.Add(value); } }