2025-01-13 17:45:49 +05:00
|
|
|
|
using DD.Persistence.Database.Entity;
|
2025-01-14 17:56:59 +05:00
|
|
|
|
using DD.Persistence.Models;
|
2025-01-13 17:45:49 +05:00
|
|
|
|
using DD.Persistence.Models.Common;
|
2024-12-16 15:38:46 +05:00
|
|
|
|
using DD.Persistence.Repositories;
|
2025-01-16 17:19:27 +05:00
|
|
|
|
using DD.Persistence.Repository.Extensions;
|
2025-01-13 17:45:49 +05:00
|
|
|
|
using Mapster;
|
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
2025-01-16 17:19:27 +05:00
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
|
using System.Linq;
|
2025-01-14 17:56:59 +05:00
|
|
|
|
using System.Text.Json;
|
2025-01-16 17:19:27 +05:00
|
|
|
|
using System.Text.Json.Nodes;
|
2024-11-14 15:17:43 +05:00
|
|
|
|
|
2024-12-16 15:38:46 +05:00
|
|
|
|
namespace DD.Persistence.Repository.Repositories;
|
2025-01-16 17:19:27 +05:00
|
|
|
|
public class TimestampedValuesRepository : ITimestampedValuesRepository
|
2024-11-14 15:17:43 +05:00
|
|
|
|
{
|
2024-12-10 10:43:12 +05:00
|
|
|
|
private readonly DbContext db;
|
2025-01-16 17:19:27 +05:00
|
|
|
|
private readonly IRelatedDataRepository<ValuesIdentityDto> relatedDataRepository;
|
2024-11-14 15:17:43 +05:00
|
|
|
|
|
2025-01-16 17:19:27 +05:00
|
|
|
|
public TimestampedValuesRepository(DbContext db, IRelatedDataRepository<ValuesIdentityDto> relatedDataRepository)
|
2024-11-14 15:17:43 +05:00
|
|
|
|
{
|
|
|
|
|
this.db = db;
|
2025-01-16 17:19:27 +05:00
|
|
|
|
this.relatedDataRepository = relatedDataRepository;
|
2024-11-14 15:17:43 +05:00
|
|
|
|
}
|
|
|
|
|
|
2025-01-16 17:19:27 +05:00
|
|
|
|
protected virtual IQueryable<TimestampedValues> GetQueryReadOnly() => this.db.Set<TimestampedValues>()
|
|
|
|
|
.Include(e => e.ValuesIdentity);
|
2024-11-14 15:17:43 +05:00
|
|
|
|
|
2025-01-14 17:56:59 +05:00
|
|
|
|
public virtual async Task<DatesRangeDto?> GetDatesRange(Guid discriminatorId, CancellationToken token)
|
2024-11-14 15:17:43 +05:00
|
|
|
|
{
|
2025-01-14 17:56:59 +05:00
|
|
|
|
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;
|
2024-11-18 14:22:09 +05:00
|
|
|
|
|
|
|
|
|
return new DatesRangeDto
|
|
|
|
|
{
|
2025-01-14 17:56:59 +05:00
|
|
|
|
From = item.Min,
|
|
|
|
|
To = item.Max,
|
2024-11-18 14:22:09 +05:00
|
|
|
|
};
|
2024-11-14 15:17:43 +05:00
|
|
|
|
}
|
|
|
|
|
|
2025-01-16 17:19:27 +05:00
|
|
|
|
public virtual async Task<IEnumerable<TimestampedValuesDto>> GetGtDate(Guid discriminatorId, DateTimeOffset date, CancellationToken token)
|
2024-11-14 15:17:43 +05:00
|
|
|
|
{
|
2025-01-13 17:45:49 +05:00
|
|
|
|
var query = GetQueryReadOnly().Where(e => e.Timestamp > date);
|
2024-11-18 14:22:09 +05:00
|
|
|
|
var entities = await query.ToArrayAsync(token);
|
|
|
|
|
|
2025-01-16 17:19:27 +05:00
|
|
|
|
var dtos = entities.Select(e => e.Adapt<TimestampedValuesDto>());
|
2024-11-18 14:22:09 +05:00
|
|
|
|
|
|
|
|
|
return dtos;
|
2024-11-14 15:17:43 +05:00
|
|
|
|
}
|
|
|
|
|
|
2025-01-16 17:19:27 +05:00
|
|
|
|
public virtual async Task<int> AddRange(Guid discriminatorId, IEnumerable<TimestampedValuesDto> dtos, CancellationToken token)
|
2024-11-14 15:17:43 +05:00
|
|
|
|
{
|
2025-01-16 17:19:27 +05:00
|
|
|
|
var timestampedValuesEntities = new List<TimestampedValues>();
|
|
|
|
|
foreach (var dto in dtos)
|
|
|
|
|
{
|
|
|
|
|
var values = dto.Values
|
|
|
|
|
.SelectMany(v => JsonSerializer.Deserialize<Dictionary<string, object>>(v.ToString()!)!)
|
|
|
|
|
.ToDictionary();
|
2025-01-14 17:56:59 +05:00
|
|
|
|
|
2025-01-16 17:19:27 +05:00
|
|
|
|
var keys = values.Keys.ToArray();
|
|
|
|
|
await CreateValuesIdentityIfNotExist(discriminatorId, keys, token);
|
|
|
|
|
|
|
|
|
|
var timestampedValuesEntity = new TimestampedValues()
|
|
|
|
|
{
|
|
|
|
|
DiscriminatorId = discriminatorId,
|
|
|
|
|
Timestamp = dto.Timestamp.ToUniversalTime(),
|
|
|
|
|
Values = values.Values.ToArray()
|
|
|
|
|
};
|
|
|
|
|
timestampedValuesEntities.Add(timestampedValuesEntity);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await db.Set<TimestampedValues>().AddRangeAsync(timestampedValuesEntities, token);
|
2024-11-14 15:17:43 +05:00
|
|
|
|
|
|
|
|
|
var result = await db.SaveChangesAsync(token);
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2024-11-19 17:51:51 +05:00
|
|
|
|
|
2025-01-16 17:19:27 +05:00
|
|
|
|
protected async Task<IEnumerable<TimestampedValuesDto>> GetLastAsync(int takeCount, CancellationToken token)
|
2024-11-19 17:51:51 +05:00
|
|
|
|
{
|
|
|
|
|
var query = GetQueryReadOnly()
|
2025-01-09 09:29:42 +05:00
|
|
|
|
.OrderByDescending(e => e.Timestamp)
|
2024-11-19 17:51:51 +05:00
|
|
|
|
.Take(takeCount);
|
|
|
|
|
|
|
|
|
|
var entities = await query.ToArrayAsync(token);
|
2025-01-16 17:19:27 +05:00
|
|
|
|
var dtos = entities.Select(e => e.Adapt<TimestampedValuesDto>());
|
2024-11-19 17:51:51 +05:00
|
|
|
|
|
|
|
|
|
return dtos;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-16 17:19:27 +05:00
|
|
|
|
protected async Task<TimestampedValuesDto?> GetFirstAsync(CancellationToken token)
|
2024-11-19 17:51:51 +05:00
|
|
|
|
{
|
|
|
|
|
var query = GetQueryReadOnly()
|
2025-01-09 09:29:42 +05:00
|
|
|
|
.OrderBy(e => e.Timestamp);
|
2024-11-19 17:51:51 +05:00
|
|
|
|
|
|
|
|
|
var entity = await query.FirstOrDefaultAsync(token);
|
|
|
|
|
|
2024-11-21 17:02:36 +05:00
|
|
|
|
if (entity == null)
|
2024-11-19 17:51:51 +05:00
|
|
|
|
return null;
|
|
|
|
|
|
2025-01-16 17:19:27 +05:00
|
|
|
|
var dto = entity.Adapt<TimestampedValuesDto>();
|
2024-11-19 17:51:51 +05:00
|
|
|
|
return dto;
|
|
|
|
|
}
|
2024-11-21 17:02:36 +05:00
|
|
|
|
|
2025-01-16 17:19:27 +05:00
|
|
|
|
public async virtual Task<IEnumerable<TimestampedValuesDto>> GetResampledData(
|
2025-01-14 17:56:59 +05:00
|
|
|
|
Guid discriminatorId,
|
2024-12-09 13:19:55 +05:00
|
|
|
|
DateTimeOffset dateBegin,
|
|
|
|
|
double intervalSec = 600d,
|
2024-11-22 16:48:55 +05:00
|
|
|
|
int approxPointsCount = 1024,
|
|
|
|
|
CancellationToken token = default)
|
2024-11-21 17:02:36 +05:00
|
|
|
|
{
|
2025-01-14 17:56:59 +05:00
|
|
|
|
var dtos = await GetGtDate(discriminatorId, dateBegin, token);
|
2024-11-22 15:47:00 +05:00
|
|
|
|
|
2024-11-22 16:48:55 +05:00
|
|
|
|
var dateEnd = dateBegin.AddSeconds(intervalSec);
|
|
|
|
|
dtos = dtos
|
2025-01-13 17:45:49 +05:00
|
|
|
|
.Where(i => i.Timestamp <= dateEnd);
|
2024-11-22 15:47:00 +05:00
|
|
|
|
|
2024-11-22 16:48:55 +05:00
|
|
|
|
var ratio = dtos.Count() / approxPointsCount;
|
|
|
|
|
if (ratio > 1)
|
|
|
|
|
dtos = dtos
|
|
|
|
|
.Where((_, index) => index % ratio == 0);
|
2024-11-22 15:47:00 +05:00
|
|
|
|
|
2024-11-22 16:48:55 +05:00
|
|
|
|
return dtos;
|
2024-11-21 17:02:36 +05:00
|
|
|
|
}
|
2025-01-14 17:56:59 +05:00
|
|
|
|
|
2025-01-16 17:19:27 +05:00
|
|
|
|
public async Task<IEnumerable<TimestampedValuesDto>> Get(Guid discriminatorId, DateTimeOffset? geTimestamp, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token)
|
2025-01-14 17:56:59 +05:00
|
|
|
|
{
|
|
|
|
|
var dbSet = db.Set<TimestampedValues>();
|
2025-01-16 17:19:27 +05:00
|
|
|
|
var query = dbSet.Where(entity => entity.DiscriminatorId == discriminatorId);
|
2025-01-14 17:56:59 +05:00
|
|
|
|
|
|
|
|
|
if (geTimestamp.HasValue)
|
|
|
|
|
query = ApplyGeTimestamp(query, geTimestamp.Value);
|
|
|
|
|
|
|
|
|
|
query = query
|
|
|
|
|
.OrderBy(item => item.Timestamp)
|
|
|
|
|
.Skip(skip)
|
|
|
|
|
.Take(take);
|
|
|
|
|
|
2025-01-16 17:19:27 +05:00
|
|
|
|
var data = await Materialize(discriminatorId, query, token);
|
2025-01-14 17:56:59 +05:00
|
|
|
|
|
|
|
|
|
if (columnNames is not null && columnNames.Any())
|
|
|
|
|
data = ReduceSetColumnsByNames(data, columnNames);
|
|
|
|
|
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-16 17:19:27 +05:00
|
|
|
|
public async Task<IEnumerable<TimestampedValuesDto>> GetLast(Guid discriminatorId, IEnumerable<string>? columnNames, int take, CancellationToken token)
|
2025-01-14 17:56:59 +05:00
|
|
|
|
{
|
|
|
|
|
var dbSet = db.Set<TimestampedValues>();
|
2025-01-16 17:19:27 +05:00
|
|
|
|
var query = dbSet.Where(entity => entity.DiscriminatorId == discriminatorId);
|
2025-01-14 17:56:59 +05:00
|
|
|
|
|
|
|
|
|
query = query.OrderByDescending(entity => entity.Timestamp)
|
|
|
|
|
.Take(take)
|
|
|
|
|
.OrderBy(entity => entity.Timestamp);
|
|
|
|
|
|
2025-01-16 17:19:27 +05:00
|
|
|
|
var data = await Materialize(discriminatorId, query, token);
|
2025-01-14 17:56:59 +05:00
|
|
|
|
|
|
|
|
|
if (columnNames is not null && columnNames.Any())
|
|
|
|
|
data = ReduceSetColumnsByNames(data, columnNames);
|
|
|
|
|
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-16 17:19:27 +05:00
|
|
|
|
public Task<int> Count(Guid discriminatorId, CancellationToken token)
|
2025-01-14 17:56:59 +05:00
|
|
|
|
{
|
|
|
|
|
var dbSet = db.Set<TimestampedValues>();
|
2025-01-16 17:19:27 +05:00
|
|
|
|
var query = dbSet.Where(entity => entity.DiscriminatorId == discriminatorId);
|
2025-01-14 17:56:59 +05:00
|
|
|
|
return query.CountAsync(token);
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-16 17:19:27 +05:00
|
|
|
|
private async Task<IEnumerable<TimestampedValuesDto>> Materialize(Guid discriminatorId, IQueryable<TimestampedValues> query, CancellationToken token)
|
2025-01-14 17:56:59 +05:00
|
|
|
|
{
|
2025-01-16 17:19:27 +05:00
|
|
|
|
var dtoQuery = query.Select(entity => new TimestampedValuesDto()
|
|
|
|
|
{
|
|
|
|
|
Timestamp = entity.Timestamp,
|
|
|
|
|
Values = entity.Values
|
|
|
|
|
});
|
|
|
|
|
|
2025-01-14 17:56:59 +05:00
|
|
|
|
var dtos = await dtoQuery.ToArrayAsync(token);
|
2025-01-16 17:19:27 +05:00
|
|
|
|
foreach(var dto in dtos)
|
|
|
|
|
{
|
|
|
|
|
var valuesIdentities = await relatedDataRepository.Get(token);
|
|
|
|
|
var valuesIdentity = valuesIdentities?
|
|
|
|
|
.FirstOrDefault(e => e.DiscriminatorId == discriminatorId);
|
|
|
|
|
if (valuesIdentity == null)
|
|
|
|
|
return []; // ToDo: какая логика должна быть?
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < valuesIdentity.Identity.Count(); i++)
|
|
|
|
|
{
|
|
|
|
|
var key = valuesIdentity.Identity[i];
|
|
|
|
|
var value = dto.Values[i];
|
|
|
|
|
|
|
|
|
|
dto.Values[i] = new { key = value }; // ToDo: вывод?
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-14 17:56:59 +05:00
|
|
|
|
return dtos;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-16 17:19:27 +05:00
|
|
|
|
private IQueryable<TimestampedValues> ApplyGeTimestamp(IQueryable<TimestampedValues> query, DateTimeOffset geTimestamp)
|
2025-01-14 17:56:59 +05:00
|
|
|
|
{
|
|
|
|
|
var geTimestampUtc = geTimestamp.ToUniversalTime();
|
|
|
|
|
return query.Where(entity => entity.Timestamp >= geTimestampUtc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static IEnumerable<TimestampedValuesDto> ReduceSetColumnsByNames(IEnumerable<TimestampedValuesDto> query, IEnumerable<string> columnNames)
|
|
|
|
|
{
|
2025-01-16 17:19:27 +05:00
|
|
|
|
var newQuery = query;
|
|
|
|
|
//.Select(entity => new TimestampedValuesDto()
|
|
|
|
|
//{
|
|
|
|
|
// Timestamp = entity.Timestamp,
|
|
|
|
|
// Values = entity.Values?
|
|
|
|
|
// .Where(prop => columnNames.Contains(
|
|
|
|
|
// JsonSerializer.Deserialize<Dictionary<string, object>>(prop.ToString()!)?
|
|
|
|
|
// .FirstOrDefault().Key
|
|
|
|
|
// )).ToArray()
|
|
|
|
|
//});
|
2025-01-14 17:56:59 +05:00
|
|
|
|
return newQuery;
|
|
|
|
|
}
|
2025-01-16 17:19:27 +05:00
|
|
|
|
|
|
|
|
|
private async Task CreateValuesIdentityIfNotExist(Guid discriminatorId, string[] keys, CancellationToken token)
|
|
|
|
|
{
|
|
|
|
|
var valuesIdentities = await relatedDataRepository.Get(token);
|
|
|
|
|
var valuesIdentity = valuesIdentities?
|
|
|
|
|
.FirstOrDefault(e => e.DiscriminatorId == discriminatorId);
|
|
|
|
|
|
|
|
|
|
if (valuesIdentity == null)
|
|
|
|
|
{
|
|
|
|
|
valuesIdentity = new ValuesIdentityDto()
|
|
|
|
|
{
|
|
|
|
|
DiscriminatorId = discriminatorId,
|
|
|
|
|
Identity = keys
|
|
|
|
|
};
|
|
|
|
|
await relatedDataRepository.Add(valuesIdentity, token);
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!valuesIdentity.Identity.SequenceEqual(keys))
|
|
|
|
|
{
|
|
|
|
|
var expectedIdentity = string.Join(", ", valuesIdentity.Identity);
|
|
|
|
|
var actualIdentity = string.Join(", ", keys);
|
|
|
|
|
throw new InvalidOperationException($"Для системы {discriminatorId.ToString()} " +
|
|
|
|
|
$"характерен набор данных: [{expectedIdentity}], однако был передан набор: [{actualIdentity}]");
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-11-14 15:17:43 +05:00
|
|
|
|
}
|