201 lines
6.9 KiB
C#
201 lines
6.9 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Text.Json;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using FinlyticCore.Entities.Settings;
|
|
using FinlyticCore.Models.Settings;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace FinlyticCore.Services;
|
|
|
|
public interface ISettingsService<TContext> where TContext : DbContext
|
|
{
|
|
// --- 1. Typsicherer Zugriff über SettingKey<T> (Empfohlen) ---
|
|
Task<T> GetSettingAsync<T>(SettingKey<T> key,
|
|
CancellationToken cancellationToken = default);
|
|
|
|
Task SetSettingAsync<T>(SettingKey<T> key, T value,
|
|
CancellationToken cancellationToken = default);
|
|
|
|
// --- 2. Dynamischer Zugriff über Enum-Key ---
|
|
Task<T> GetSettingAsync<TEnum, T>(TEnum enumKey, T defaultValue = default!,
|
|
CancellationToken cancellationToken = default) where TEnum : struct, Enum;
|
|
|
|
Task SetSettingAsync<TEnum, T>(TEnum enumKey, T value,
|
|
CancellationToken cancellationToken = default) where TEnum : struct, Enum;
|
|
|
|
// --- 3. Dynamischer Zugriff über String-Key ---
|
|
Task<T> GetSettingAsync<T>(string key, T defaultValue = default!,
|
|
CancellationToken cancellationToken = default);
|
|
|
|
Task SetSettingAsync<T>(string key, T value,
|
|
CancellationToken cancellationToken = default);
|
|
}
|
|
|
|
public class SettingsService<TContext> : ISettingsService<TContext> where TContext : DbContext
|
|
{
|
|
private readonly Microsoft.Extensions.DependencyInjection.IServiceScopeFactory _scopeFactory;
|
|
private readonly ILogger<SettingsService<TContext>>? _logger;
|
|
|
|
// Fast In-Memory Cache: Key Schema: "KeyName"
|
|
private readonly ConcurrentDictionary<string, string> _cache = new();
|
|
|
|
public SettingsService(Microsoft.Extensions.DependencyInjection.IServiceScopeFactory scopeFactory, ILogger<SettingsService<TContext>>? logger = null)
|
|
{
|
|
_scopeFactory = scopeFactory;
|
|
_logger = logger;
|
|
}
|
|
|
|
#region SettingKey<T> Overloads
|
|
|
|
public Task<T> GetSettingAsync<T>(SettingKey<T> key, CancellationToken cancellationToken = default)
|
|
{
|
|
return GetSettingInternalAsync(key.Name, key.DefaultValue, cancellationToken);
|
|
}
|
|
|
|
public Task SetSettingAsync<T>(SettingKey<T> key, T value, CancellationToken cancellationToken = default)
|
|
{
|
|
return SetSettingInternalAsync(key.Name, value, cancellationToken);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Enum-Key Overloads
|
|
|
|
public Task<T> GetSettingAsync<TEnum, T>(TEnum enumKey, T defaultValue = default!,
|
|
CancellationToken cancellationToken = default)
|
|
where TEnum : struct, Enum
|
|
{
|
|
var keyName = $"{typeof(TEnum).Name}.{enumKey}";
|
|
return GetSettingInternalAsync(keyName, defaultValue, cancellationToken);
|
|
}
|
|
|
|
public Task SetSettingAsync<TEnum, T>(TEnum enumKey, T value, CancellationToken cancellationToken = default)
|
|
where TEnum : struct, Enum
|
|
{
|
|
var keyName = $"{typeof(TEnum).Name}.{enumKey}";
|
|
return SetSettingInternalAsync(keyName, value, cancellationToken);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region String-Key Overloads
|
|
|
|
public Task<T> GetSettingAsync<T>(string key, T defaultValue = default!,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return GetSettingInternalAsync(key, defaultValue, cancellationToken);
|
|
}
|
|
|
|
public Task SetSettingAsync<T>(string key, T value, CancellationToken cancellationToken = default)
|
|
{
|
|
return SetSettingInternalAsync(key, value, cancellationToken);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Core Engine Logik
|
|
|
|
private async Task<T> GetSettingInternalAsync<T>(string key, T defaultValue, CancellationToken cancellationToken)
|
|
{
|
|
// 1. Zuerst im In-Memory Cache prüfen
|
|
if (_cache.TryGetValue(key, out var cachedJson))
|
|
{
|
|
return DeserializeValue(cachedJson, defaultValue);
|
|
}
|
|
|
|
try
|
|
{
|
|
using var scope = _scopeFactory.CreateScope();
|
|
var dbContext = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<TContext>(scope.ServiceProvider);
|
|
|
|
// 2. Aus DB der spezifischen TContext-Instanz laden
|
|
var entity = await dbContext.Set<SettingEntity>()
|
|
.FirstOrDefaultAsync(s => s.Key == key, cancellationToken);
|
|
|
|
// 3. Falls noch nicht vorhanden: In DB anlegen (Seed on Demand)
|
|
if (entity == null)
|
|
{
|
|
var defaultJson = JsonSerializer.Serialize(defaultValue);
|
|
entity = new SettingEntity
|
|
{
|
|
Key = key,
|
|
ValueJson = defaultJson,
|
|
LastUpdatedUtc = DateTime.UtcNow
|
|
};
|
|
|
|
dbContext.Set<SettingEntity>().Add(entity);
|
|
await dbContext.SaveChangesAsync(cancellationToken);
|
|
|
|
_cache[key] = defaultJson;
|
|
return defaultValue;
|
|
}
|
|
|
|
// In Cache legen & Wert zurückgeben
|
|
_cache[key] = entity.ValueJson;
|
|
return DeserializeValue(entity.ValueJson, defaultValue);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogWarning(ex, "Setting '{Key}' could not be loaded or initialized in DB. Using default value in memory.", key);
|
|
_cache[key] = JsonSerializer.Serialize(defaultValue);
|
|
return defaultValue;
|
|
}
|
|
}
|
|
|
|
private async Task SetSettingInternalAsync<T>(string key, T value, CancellationToken cancellationToken)
|
|
{
|
|
var jsonValue = JsonSerializer.Serialize(value);
|
|
|
|
try
|
|
{
|
|
using var scope = _scopeFactory.CreateScope();
|
|
var dbContext = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<TContext>(scope.ServiceProvider);
|
|
|
|
var entity = await dbContext.Set<SettingEntity>()
|
|
.FirstOrDefaultAsync(s => s.Key == key, cancellationToken);
|
|
|
|
if (entity == null)
|
|
{
|
|
entity = new SettingEntity
|
|
{
|
|
Key = key,
|
|
ValueJson = jsonValue,
|
|
LastUpdatedUtc = DateTime.UtcNow
|
|
};
|
|
dbContext.Set<SettingEntity>().Add(entity);
|
|
}
|
|
else
|
|
{
|
|
entity.ValueJson = jsonValue;
|
|
entity.LastUpdatedUtc = DateTime.UtcNow;
|
|
}
|
|
|
|
await dbContext.SaveChangesAsync(cancellationToken);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogWarning(ex, "Setting '{Key}' could not be saved to DB.", key);
|
|
}
|
|
|
|
// Cache trotzdem aktualisieren
|
|
_cache[key] = jsonValue;
|
|
}
|
|
|
|
private static T DeserializeValue<T>(string json, T defaultValue)
|
|
{
|
|
try
|
|
{
|
|
var result = JsonSerializer.Deserialize<T>(json);
|
|
return result ?? defaultValue;
|
|
}
|
|
catch
|
|
{
|
|
return defaultValue;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
} |