using Dapper; using MySqlConnector; using IBKRTrader.Core.Logging; using IBKRTrader.Core.Settings; namespace IBKRTrader.Core.Database; /// /// Zentraler Datenbankzugriff via MySqlConnector + Dapper. /// Jede Methode öffnet eine eigene Connection (Connection-Pooling via MySqlConnector). /// public class DatabaseService { private readonly SettingsService _settings; private readonly LoggingService _logger; public DatabaseService(SettingsService settings, LoggingService logger) { _settings = settings; _logger = logger; // Dapper support for DateOnly SqlMapper.AddTypeHandler(new DateOnlyTypeHandler()); } private class DateOnlyTypeHandler : SqlMapper.TypeHandler { public override void SetValue(System.Data.IDbDataParameter parameter, DateOnly value) { parameter.Value = value.ToString("yyyy-MM-dd"); } public override DateOnly Parse(object value) { if (value is DateTime dt) return DateOnly.FromDateTime(dt); if (value is string s && DateOnly.TryParse(s, out var d)) return d; return DateOnly.FromDateTime(Convert.ToDateTime(value)); } } // ─── Connection ─────────────────────────────────────────────────────────── public MySqlConnection CreateConnection() => new(_settings.Settings.Database.BuildConnectionString()); public async Task TestConnectionAsync() { try { await using var conn = CreateConnection(); await conn.OpenAsync(); _logger.Info("Core", "Datenbankverbindung erfolgreich hergestellt."); return true; } catch (Exception ex) { _logger.Error("Core", "Datenbankverbindung fehlgeschlagen.", ex); return false; } } // ─── Abfragen (Dapper) ──────────────────────────────────────────────────── public async Task> QueryAsync(string sql, object? param = null) { await using var conn = CreateConnection(); return await conn.QueryAsync(sql, param); } public async Task QueryFirstOrDefaultAsync(string sql, object? param = null) { await using var conn = CreateConnection(); return await conn.QueryFirstOrDefaultAsync(sql, param); } public async Task ExecuteAsync(string sql, object? param = null) { await using var conn = CreateConnection(); return await conn.ExecuteAsync(sql, param); } public async Task ExecuteScalarAsync(string sql, object? param = null) { await using var conn = CreateConnection(); #pragma warning disable CS8603 return await conn.ExecuteScalarAsync(sql, param); #pragma warning restore CS8603 } // ─── Worker-Log Hilfsmethoden ───────────────────────────────────────────── /// Legt einen neuen Worker-Log-Eintrag an und gibt die neue ID zurück. public async Task BeginWorkerLogAsync(string workerName, string module) { const string sql = @"INSERT INTO `core_worker_log` (worker_name, module, started_at, status) VALUES (@workerName, @module, @now, 'Running'); SELECT LAST_INSERT_ID();"; return await ExecuteScalarAsync(sql, new { workerName, module, now = DateTime.UtcNow }); } /// Schließt einen Worker-Log-Eintrag ab (Success oder Error). public async Task EndWorkerLogAsync(long logId, bool success, string? message = null) { const string sql = @"UPDATE `core_worker_log` SET finished_at = @now, status = @status, message = @msg WHERE id = @id"; await ExecuteAsync(sql, new { id = logId, now = DateTime.UtcNow, status = success ? "Success" : "Error", msg = message }); } }