using Microsoft.EntityFrameworkCore; using DD.Persistence.Database.Entity; using DD.Persistence.Models; using DD.Persistence.Repositories; namespace DD.Persistence.Repository.Repositories; /// <summary> /// Репозиторий для хранения разных наборов данных временных рядов. /// idDiscriminator - идентифицирует конкретный набор данных, прим.: циклы измерения АСИБР, или отчет о DrillTest. /// idDiscriminator формируют клиенты и только им известно что они обозначают. /// Так как данные приходят редко, то их прореживания для построения графиков не предусмотрено. /// </summary> public class TimestampedSetRepository : ITimestampedSetRepository { private readonly DbContext db; public TimestampedSetRepository(DbContext db) { this.db = db; } public Task<int> AddRange(Guid idDiscriminator, IEnumerable<TimestampedSetDto> sets, CancellationToken token) { var entities = sets.Select(set => new TimestampedSet(idDiscriminator, set.Timestamp.ToUniversalTime(), set.Set)); var dbSet = db.Set<TimestampedSet>(); dbSet.AddRange(entities); return db.SaveChangesAsync(token); } public async Task<IEnumerable<TimestampedSetDto>> Get(Guid idDiscriminator, DateTimeOffset? geTimestamp, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token) { var dbSet = db.Set<TimestampedSet>(); var query = dbSet.Where(entity => entity.IdDiscriminator == 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<IEnumerable<TimestampedSetDto>> GetLast(Guid idDiscriminator, IEnumerable<string>? columnNames, int take, CancellationToken token) { var dbSet = db.Set<TimestampedSet>(); var query = dbSet.Where(entity => entity.IdDiscriminator == 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<int> Count(Guid idDiscriminator, CancellationToken token) { var dbSet = db.Set<TimestampedSet>(); var query = dbSet.Where(entity => entity.IdDiscriminator == idDiscriminator); return query.CountAsync(token); } public async Task<DatesRangeDto?> GetDatesRange(Guid idDiscriminator, CancellationToken token) { var query = db.Set<TimestampedSet>() .GroupBy(entity => entity.IdDiscriminator) .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, }; } private static async Task<IEnumerable<TimestampedSetDto>> Materialize(IQueryable<TimestampedSet> query, CancellationToken token) { var dtoQuery = query.Select(entity => new TimestampedSetDto(entity.Timestamp, entity.Set)); var dtos = await dtoQuery.ToArrayAsync(token); return dtos; } private static IQueryable<TimestampedSet> ApplyGeTimestamp(IQueryable<TimestampedSet> query, DateTimeOffset geTimestamp) { var geTimestampUtc = geTimestamp.ToUniversalTime(); return query.Where(entity => entity.Timestamp >= geTimestampUtc); } private static IEnumerable<TimestampedSetDto> ReduceSetColumnsByNames(IEnumerable<TimestampedSetDto> query, IEnumerable<string> columnNames) { var newQuery = query .Select(entity => new TimestampedSetDto( entity.Timestamp, entity.Set .Where(prop => columnNames.Contains(prop.Key)) .ToDictionary(prop => prop.Key, prop => prop.Value) )); return newQuery; } }