using DD.Persistence.Database.Entity; using DD.Persistence.Models; using DD.Persistence.Models.Common; using DD.Persistence.ModelsAbstractions; using DD.Persistence.Repositories; using Mapster; using Microsoft.EntityFrameworkCore; using System.Text.Json; namespace DD.Persistence.Repository.Repositories; public class TimestampedValuesRepository : ITimestampedValuesRepository where TDto : class, ITimestampAbstractDto, new() { private readonly DbContext db; public TimestampedValuesRepository(DbContext db) { this.db = db; } protected virtual IQueryable GetQueryReadOnly() => this.db.Set(); public virtual async Task GetDatesRange(Guid discriminatorId, CancellationToken token) { var query = GetQueryReadOnly() .GroupBy(entity => entity.DiscriminatorId) .Select(group => new { Min = group.Min(entity => entity.Timestamp), Max = group.Max(entity => entity.Timestamp), }); var item = await query.FirstOrDefaultAsync(token); if (item is null) return null; return new DatesRangeDto { From = item.Min, To = item.Max, }; } public virtual async Task> GetGtDate(Guid discriminatorId, DateTimeOffset date, CancellationToken token) { var query = GetQueryReadOnly().Where(e => e.Timestamp > date); var entities = await query.ToArrayAsync(token); var dtos = entities.Select(e => e.Adapt()); return dtos; } public virtual async Task AddRange(Guid discriminatorId, IEnumerable dtos, CancellationToken token) { var entities = dtos .Select(d => d.Adapt()) .ToList(); entities.ForEach(d => d.DiscriminatorId = discriminatorId); await db.Set().AddRangeAsync(entities, token); var result = await db.SaveChangesAsync(token); return result; } protected async Task> GetLastAsync(int takeCount, CancellationToken token) { var query = GetQueryReadOnly() .OrderByDescending(e => e.Timestamp) .Take(takeCount); var entities = await query.ToArrayAsync(token); var dtos = entities.Select(e => e.Adapt()); return dtos; } protected async Task GetFirstAsync(CancellationToken token) { var query = GetQueryReadOnly() .OrderBy(e => e.Timestamp); var entity = await query.FirstOrDefaultAsync(token); if (entity == null) return null; var dto = entity.Adapt(); return dto; } public async virtual Task> GetResampledData( Guid discriminatorId, DateTimeOffset dateBegin, double intervalSec = 600d, int approxPointsCount = 1024, CancellationToken token = default) { var dtos = await GetGtDate(discriminatorId, dateBegin, token); var dateEnd = dateBegin.AddSeconds(intervalSec); dtos = dtos .Where(i => i.Timestamp <= dateEnd); var ratio = dtos.Count() / approxPointsCount; if (ratio > 1) dtos = dtos .Where((_, index) => index % ratio == 0); return dtos; } public async Task> Get(Guid idDiscriminator, DateTimeOffset? geTimestamp, IEnumerable? columnNames, int skip, int take, CancellationToken token) { var dbSet = db.Set(); var query = dbSet.Where(entity => entity.DiscriminatorId == idDiscriminator); if (geTimestamp.HasValue) query = ApplyGeTimestamp(query, geTimestamp.Value); query = query .OrderBy(item => item.Timestamp) .Skip(skip) .Take(take); var data = await Materialize(query, token); if (columnNames is not null && columnNames.Any()) data = ReduceSetColumnsByNames(data, columnNames); return data; } public async Task> GetLast(Guid idDiscriminator, IEnumerable? columnNames, int take, CancellationToken token) { var dbSet = db.Set(); var query = dbSet.Where(entity => entity.DiscriminatorId == idDiscriminator); query = query.OrderByDescending(entity => entity.Timestamp) .Take(take) .OrderBy(entity => entity.Timestamp); var data = await Materialize(query, token); if (columnNames is not null && columnNames.Any()) data = ReduceSetColumnsByNames(data, columnNames); return data; } public Task Count(Guid idDiscriminator, CancellationToken token) { var dbSet = db.Set(); var query = dbSet.Where(entity => entity.DiscriminatorId == idDiscriminator); return query.CountAsync(token); } private static async Task> Materialize(IQueryable query, CancellationToken token) { var dtoQuery = query.Select(entity => new TimestampedValuesDto() { Timestamp = entity.Timestamp, Values = entity.Values }); var dtos = await dtoQuery.ToArrayAsync(token); return dtos; } private static IQueryable ApplyGeTimestamp(IQueryable query, DateTimeOffset geTimestamp) { var geTimestampUtc = geTimestamp.ToUniversalTime(); return query.Where(entity => entity.Timestamp >= geTimestampUtc); } private static IEnumerable ReduceSetColumnsByNames(IEnumerable query, IEnumerable columnNames) { var newQuery = query .Select(entity => new TimestampedValuesDto() { Timestamp = entity.Timestamp, Values = entity.Values? .Where(prop => columnNames.Contains( JsonSerializer.Deserialize>(prop.ToString()!)? .FirstOrDefault().Key )).ToArray() }); return newQuery; } }