using Microsoft.EntityFrameworkCore;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Kpi;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Kpi;
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Repositories;
///
/// EF Core implementation of over the central
/// KpiSample table (M6 "KPI History & Trends"). See the interface for the
/// contract; this class adds notes on the data-access strategy per method.
///
public sealed class KpiHistoryRepository : IKpiHistoryRepository
{
private readonly ScadaBridgeDbContext _context;
///
/// Initializes a new instance of the class.
///
/// The EF Core database context.
public KpiHistoryRepository(ScadaBridgeDbContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
///
public async Task RecordSamplesAsync(
IReadOnlyCollection samples, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(samples);
// Avoid a no-op SaveChanges round-trip on quiet sampling ticks.
if (samples.Count == 0)
{
return;
}
// Bulk-insert one sampling pass. AddRange + a single SaveChanges keeps the
// whole batch in one round-trip; the store assigns each row's identity.
_context.KpiSamples.AddRange(samples);
await _context.SaveChangesAsync(cancellationToken);
}
///
public async Task> GetRawSeriesAsync(
string source, string metric, string scope, string? scopeKey,
DateTime fromUtc, DateTime toUtc, CancellationToken cancellationToken = default)
{
// The ScopeKey == scopeKey comparison is intentional: when scopeKey is null
// EF translates it to "ScopeKey IS NULL", which matches the Global-scope rows
// (null key) and excludes the site/node-scoped rows that carry a non-null key.
return await _context.KpiSamples
.Where(s => s.Source == source
&& s.Metric == metric
&& s.Scope == scope
&& s.ScopeKey == scopeKey
&& s.CapturedAtUtc >= fromUtc
&& s.CapturedAtUtc <= toUtc)
.OrderBy(s => s.CapturedAtUtc)
.Select(s => new KpiSeriesPoint(s.CapturedAtUtc, s.Value))
.ToListAsync(cancellationToken);
}
///
public async Task PurgeOlderThanAsync(DateTime before, CancellationToken cancellationToken = default)
{
// Set-based delete — no entity materialisation; returns the rows affected.
return await _context.KpiSamples
.Where(s => s.CapturedAtUtc < before)
.ExecuteDeleteAsync(cancellationToken);
}
}