2025-01-20 17:11:44 +05:00
|
|
|
|
using DD.Persistence.Extensions;
|
|
|
|
|
using DD.Persistence.Models;
|
|
|
|
|
using DD.Persistence.Repositories;
|
|
|
|
|
using DD.Persistence.Services.Interfaces;
|
2025-02-05 14:30:36 +05:00
|
|
|
|
using System.Text.Json;
|
2025-01-20 17:11:44 +05:00
|
|
|
|
|
|
|
|
|
namespace DD.Persistence.Services;
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
|
public class TimestampedValuesService : ITimestampedValuesService
|
|
|
|
|
{
|
|
|
|
|
private readonly ITimestampedValuesRepository timestampedValuesRepository;
|
2025-02-05 17:20:18 +05:00
|
|
|
|
private readonly ISchemePropertyRepository dataSchemeRepository;
|
2025-01-20 17:11:44 +05:00
|
|
|
|
|
|
|
|
|
/// <inheritdoc/>
|
2025-02-05 17:20:18 +05:00
|
|
|
|
public TimestampedValuesService(ITimestampedValuesRepository timestampedValuesRepository, ISchemePropertyRepository relatedDataRepository)
|
2025-01-20 17:11:44 +05:00
|
|
|
|
{
|
|
|
|
|
this.timestampedValuesRepository = timestampedValuesRepository;
|
|
|
|
|
this.dataSchemeRepository = relatedDataRepository;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
|
public async Task<int> AddRange(Guid discriminatorId, IEnumerable<TimestampedValuesDto> dtos, CancellationToken token)
|
|
|
|
|
{
|
2025-01-22 15:16:24 +05:00
|
|
|
|
// ToDo: реализовать без foreach
|
2025-01-20 17:11:44 +05:00
|
|
|
|
foreach (var dto in dtos)
|
|
|
|
|
{
|
2025-02-05 09:56:49 +05:00
|
|
|
|
await CreateDataSchemeIfNotExist(discriminatorId, dto, token);
|
2025-01-20 17:11:44 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var result = await timestampedValuesRepository.AddRange(discriminatorId, dtos, token);
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-23 17:33:01 +05:00
|
|
|
|
/// <inheritdoc/>
|
|
|
|
|
public async Task<IEnumerable<TimestampedValuesDto>> Get(IEnumerable<Guid> discriminatorIds, DateTimeOffset? geTimestamp, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token)
|
|
|
|
|
{
|
|
|
|
|
var result = await timestampedValuesRepository.Get(discriminatorIds, geTimestamp, columnNames, skip, take, token);
|
|
|
|
|
|
2025-02-05 17:20:18 +05:00
|
|
|
|
var dtos = await BindingToDataScheme(result, token);
|
2025-01-23 17:33:01 +05:00
|
|
|
|
|
|
|
|
|
if (!columnNames.IsNullOrEmpty())
|
|
|
|
|
{
|
|
|
|
|
dtos = ReduceSetColumnsByNames(dtos, columnNames!).ToList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return dtos;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-20 17:11:44 +05:00
|
|
|
|
/// <inheritdoc/>
|
|
|
|
|
public async Task<IEnumerable<TimestampedValuesDto>> GetFirst(Guid discriminatorId, int takeCount, CancellationToken token)
|
|
|
|
|
{
|
|
|
|
|
var result = await timestampedValuesRepository.GetFirst(discriminatorId, takeCount, token);
|
|
|
|
|
|
2025-02-05 17:20:18 +05:00
|
|
|
|
var resultBeforeBinding = new[] { KeyValuePair.Create(discriminatorId, result) }
|
2025-01-24 15:40:14 +05:00
|
|
|
|
.ToDictionary();
|
2025-02-05 17:20:18 +05:00
|
|
|
|
var dtos = await BindingToDataScheme(resultBeforeBinding, token);
|
2025-01-20 17:11:44 +05:00
|
|
|
|
|
|
|
|
|
return dtos;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
|
public async Task<IEnumerable<TimestampedValuesDto>> GetLast(Guid discriminatorId, int takeCount, CancellationToken token)
|
|
|
|
|
{
|
|
|
|
|
var result = await timestampedValuesRepository.GetLast(discriminatorId, takeCount, token);
|
|
|
|
|
|
2025-02-05 17:20:18 +05:00
|
|
|
|
var resultBeforeBinding = new[] { KeyValuePair.Create(discriminatorId, result) }
|
2025-01-24 15:40:14 +05:00
|
|
|
|
.ToDictionary();
|
2025-02-05 17:20:18 +05:00
|
|
|
|
var dtos = await BindingToDataScheme(resultBeforeBinding, token);
|
2025-01-20 17:11:44 +05:00
|
|
|
|
|
|
|
|
|
return dtos;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
|
public async Task<IEnumerable<TimestampedValuesDto>> GetResampledData(
|
|
|
|
|
Guid discriminatorId,
|
|
|
|
|
DateTimeOffset beginTimestamp,
|
|
|
|
|
double intervalSec = 600d,
|
|
|
|
|
int approxPointsCount = 1024,
|
|
|
|
|
CancellationToken token = default)
|
|
|
|
|
{
|
|
|
|
|
var result = await timestampedValuesRepository.GetResampledData(discriminatorId, beginTimestamp, intervalSec, approxPointsCount, token);
|
|
|
|
|
|
2025-02-05 17:20:18 +05:00
|
|
|
|
var resultBeforeBinding = new[] { KeyValuePair.Create(discriminatorId, result) }
|
2025-01-24 15:40:14 +05:00
|
|
|
|
.ToDictionary();
|
2025-02-05 17:20:18 +05:00
|
|
|
|
var dtos = await BindingToDataScheme(resultBeforeBinding, token);
|
2025-01-20 17:11:44 +05:00
|
|
|
|
|
|
|
|
|
return dtos;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
|
public async Task<IEnumerable<TimestampedValuesDto>> GetGtDate(Guid discriminatorId, DateTimeOffset beginTimestamp, CancellationToken token)
|
|
|
|
|
{
|
|
|
|
|
var result = await timestampedValuesRepository.GetGtDate(discriminatorId, beginTimestamp, token);
|
2025-02-05 09:56:49 +05:00
|
|
|
|
|
2025-02-05 17:20:18 +05:00
|
|
|
|
var resultBeforeBinding = new[] { KeyValuePair.Create(discriminatorId, result) }
|
2025-01-24 15:40:14 +05:00
|
|
|
|
.ToDictionary();
|
2025-02-05 17:20:18 +05:00
|
|
|
|
var dtos = await BindingToDataScheme(resultBeforeBinding, token);
|
2025-01-20 17:11:44 +05:00
|
|
|
|
|
|
|
|
|
return dtos;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-05 14:30:36 +05:00
|
|
|
|
// ToDo: рефакторинг, переименовать (текущее название не отражает суть)
|
2025-01-20 17:11:44 +05:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Преобразовать результат запроса в набор dto
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="queryResult"></param>
|
|
|
|
|
/// <param name="token"></param>
|
|
|
|
|
/// <returns></returns>
|
2025-02-05 17:20:18 +05:00
|
|
|
|
private async Task<IEnumerable<TimestampedValuesDto>> BindingToDataScheme(IDictionary<Guid, IEnumerable<(DateTimeOffset Timestamp, object[] Values)>> queryResult, CancellationToken token)
|
2025-01-20 17:11:44 +05:00
|
|
|
|
{
|
2025-01-24 15:40:14 +05:00
|
|
|
|
IEnumerable<TimestampedValuesDto> result = [];
|
|
|
|
|
foreach (var keyValuePair in queryResult)
|
2025-01-20 17:11:44 +05:00
|
|
|
|
{
|
2025-01-24 15:40:14 +05:00
|
|
|
|
var dataScheme = await dataSchemeRepository.Get(keyValuePair.Key, token);
|
|
|
|
|
if (dataScheme is null)
|
|
|
|
|
continue;
|
2025-01-20 17:11:44 +05:00
|
|
|
|
|
2025-02-06 12:39:40 +05:00
|
|
|
|
foreach (var (Timestamp, Values) in keyValuePair.Value)
|
2025-01-24 15:40:14 +05:00
|
|
|
|
{
|
|
|
|
|
var dto = new TimestampedValuesDto()
|
|
|
|
|
{
|
2025-02-06 12:39:40 +05:00
|
|
|
|
Timestamp = Timestamp.ToUniversalTime(),
|
2025-02-05 17:20:18 +05:00
|
|
|
|
Values = dataScheme
|
2025-02-06 12:39:40 +05:00
|
|
|
|
.ToDictionary(k => k.PropertyName, v => Values[v.Index])
|
2025-01-24 15:40:14 +05:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
result = result.Append(dto);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-01-20 17:11:44 +05:00
|
|
|
|
|
2025-01-24 15:40:14 +05:00
|
|
|
|
return result;
|
2025-01-20 17:11:44 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2025-02-05 09:56:49 +05:00
|
|
|
|
/// Создать схему данных, при отсутствии таковой
|
2025-01-20 17:11:44 +05:00
|
|
|
|
/// </summary>
|
2025-02-05 09:56:49 +05:00
|
|
|
|
/// <param name="discriminatorId">Дискриминатор схемы</param>
|
|
|
|
|
/// <param name="dto">Набор данных, по образу которого будет создана соответствующая схема</param>
|
2025-01-20 17:11:44 +05:00
|
|
|
|
/// <param name="token"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
/// <exception cref="InvalidOperationException">Некорректный набор наименований полей</exception>
|
2025-02-05 09:56:49 +05:00
|
|
|
|
private async Task CreateDataSchemeIfNotExist(Guid discriminatorId, TimestampedValuesDto dto, CancellationToken token)
|
2025-01-20 17:11:44 +05:00
|
|
|
|
{
|
2025-02-05 17:20:18 +05:00
|
|
|
|
var valuesList = dto.Values.ToList();
|
2025-02-06 12:39:40 +05:00
|
|
|
|
var properties = valuesList.Select((e, index) => new SchemePropertyDto()
|
|
|
|
|
{
|
|
|
|
|
Index = index,
|
2025-02-05 17:20:18 +05:00
|
|
|
|
PropertyName = e.Key,
|
2025-02-06 12:39:40 +05:00
|
|
|
|
PropertyKind = ((JsonElement)e.Value).ValueKind
|
|
|
|
|
});
|
2025-02-05 09:56:49 +05:00
|
|
|
|
|
|
|
|
|
var dataScheme = await dataSchemeRepository.Get(discriminatorId, token);
|
|
|
|
|
if (dataScheme is null)
|
2025-01-20 17:11:44 +05:00
|
|
|
|
{
|
2025-02-05 17:20:18 +05:00
|
|
|
|
dataScheme = new DataSchemeDto(discriminatorId, properties);
|
|
|
|
|
await dataSchemeRepository.AddRange(dataScheme, token);
|
2025-01-20 17:11:44 +05:00
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-05 17:20:18 +05:00
|
|
|
|
if (!dataScheme.Equals(properties))
|
2025-01-20 17:11:44 +05:00
|
|
|
|
{
|
|
|
|
|
throw new InvalidOperationException($"Для системы {discriminatorId.ToString()} " +
|
2025-02-05 17:20:18 +05:00
|
|
|
|
$"был передан нехарактерный набор данных");
|
2025-01-20 17:11:44 +05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Отсеить лишние поля в соответствии с заданным фильтром
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="dtos"></param>
|
|
|
|
|
/// <param name="fieldNames">Поля, которые необходимо оставить</param>
|
|
|
|
|
/// <returns></returns>
|
2025-02-06 12:39:40 +05:00
|
|
|
|
private static IEnumerable<TimestampedValuesDto> ReduceSetColumnsByNames(IEnumerable<TimestampedValuesDto> dtos, IEnumerable<string> fieldNames)
|
2025-01-20 17:11:44 +05:00
|
|
|
|
{
|
|
|
|
|
var result = dtos.Select(dto =>
|
|
|
|
|
{
|
|
|
|
|
var reducedValues = dto.Values
|
|
|
|
|
.Where(v => fieldNames.Contains(v.Key))
|
|
|
|
|
.ToDictionary();
|
|
|
|
|
dto.Values = reducedValues;
|
|
|
|
|
|
|
|
|
|
return dto;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
}
|