Правки по результатам ревью #1

This commit is contained in:
Roman Efremov 2025-01-20 17:11:44 +05:00
parent d8497ce46a
commit 16a14e7e19
29 changed files with 580 additions and 222 deletions

View File

@ -14,9 +14,9 @@ namespace DD.Persistence.API.Controllers;
[Route("api/[controller]")] [Route("api/[controller]")]
public class DataSourceSystemController : ControllerBase public class DataSourceSystemController : ControllerBase
{ {
private readonly IRelatedDataRepository<DataSourceSystemDto> dataSourceSystemRepository; private readonly IDataSourceSystemRepository dataSourceSystemRepository;
public DataSourceSystemController(IRelatedDataRepository<DataSourceSystemDto> dataSourceSystemRepository) public DataSourceSystemController(IDataSourceSystemRepository dataSourceSystemRepository)
{ {
this.dataSourceSystemRepository = dataSourceSystemRepository; this.dataSourceSystemRepository = dataSourceSystemRepository;
} }

View File

@ -1,6 +1,7 @@
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Models.Common; using DD.Persistence.Models.Common;
using DD.Persistence.Repositories; using DD.Persistence.Services.Interfaces;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.Net; using System.Net;
@ -10,13 +11,13 @@ namespace DD.Persistence.API.Controllers;
/// Хранение наборов данных с отметкой времени. /// Хранение наборов данных с отметкой времени.
/// </summary> /// </summary>
[ApiController] [ApiController]
//[Authorize] [Authorize]
[Route("api/[controller]/{discriminatorId}")] [Route("api/[controller]/{discriminatorId}")]
public class TimestampedValuesController : ControllerBase public class TimestampedValuesController : ControllerBase
{ {
private readonly ITimestampedValuesRepository timestampedValuesRepository; private readonly ITimestampedValuesService timestampedValuesRepository;
public TimestampedValuesController(ITimestampedValuesRepository repository) public TimestampedValuesController(ITimestampedValuesService repository)
{ {
this.timestampedValuesRepository = repository; this.timestampedValuesRepository = repository;
} }
@ -47,6 +48,8 @@ public class TimestampedValuesController : ControllerBase
/// <param name="take"></param> /// <param name="take"></param>
/// <param name="token"></param> /// <param name="token"></param>
[HttpGet] [HttpGet]
[ProducesResponseType(typeof(IEnumerable<TimestampedValuesDto>), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
public async Task<ActionResult<IEnumerable<TimestampedValuesDto>>> Get([FromRoute] Guid discriminatorId, DateTimeOffset? timestampBegin, [FromQuery] string[]? columnNames, int skip, int take, CancellationToken token) public async Task<ActionResult<IEnumerable<TimestampedValuesDto>>> Get([FromRoute] Guid discriminatorId, DateTimeOffset? timestampBegin, [FromQuery] string[]? columnNames, int skip, int take, CancellationToken token)
{ {
var result = await timestampedValuesRepository.Get(discriminatorId, timestampBegin, columnNames, skip, take, token); var result = await timestampedValuesRepository.Get(discriminatorId, timestampBegin, columnNames, skip, take, token);
@ -61,6 +64,8 @@ public class TimestampedValuesController : ControllerBase
/// <param name="timestampBegin">Фильтр позднее даты</param> /// <param name="timestampBegin">Фильтр позднее даты</param>
/// <param name="token"></param> /// <param name="token"></param>
[HttpGet("gtdate")] [HttpGet("gtdate")]
[ProducesResponseType(typeof(IEnumerable<TimestampedValuesDto>), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
public async Task<ActionResult<IEnumerable<TimestampedValuesDto>>> GetGtDate([FromRoute] Guid discriminatorId, DateTimeOffset timestampBegin, CancellationToken token) public async Task<ActionResult<IEnumerable<TimestampedValuesDto>>> GetGtDate([FromRoute] Guid discriminatorId, DateTimeOffset timestampBegin, CancellationToken token)
{ {
var result = await timestampedValuesRepository.GetGtDate(discriminatorId, timestampBegin, token); var result = await timestampedValuesRepository.GetGtDate(discriminatorId, timestampBegin, token);
@ -75,6 +80,8 @@ public class TimestampedValuesController : ControllerBase
/// <param name="take"></param> /// <param name="take"></param>
/// <param name="token"></param> /// <param name="token"></param>
[HttpGet("first")] [HttpGet("first")]
[ProducesResponseType(typeof(IEnumerable<TimestampedValuesDto>), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
public async Task<ActionResult<IEnumerable<TimestampedValuesDto>>> GetFirst([FromRoute] Guid discriminatorId, int take, CancellationToken token) public async Task<ActionResult<IEnumerable<TimestampedValuesDto>>> GetFirst([FromRoute] Guid discriminatorId, int take, CancellationToken token)
{ {
var result = await timestampedValuesRepository.GetFirst(discriminatorId, take, token); var result = await timestampedValuesRepository.GetFirst(discriminatorId, take, token);
@ -89,6 +96,8 @@ public class TimestampedValuesController : ControllerBase
/// <param name="take"></param> /// <param name="take"></param>
/// <param name="token"></param> /// <param name="token"></param>
[HttpGet("last")] [HttpGet("last")]
[ProducesResponseType(typeof(IEnumerable<TimestampedValuesDto>), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
public async Task<ActionResult<IEnumerable<TimestampedValuesDto>>> GetLast([FromRoute] Guid discriminatorId, int take, CancellationToken token) public async Task<ActionResult<IEnumerable<TimestampedValuesDto>>> GetLast([FromRoute] Guid discriminatorId, int take, CancellationToken token)
{ {
var result = await timestampedValuesRepository.GetLast(discriminatorId, take, token); var result = await timestampedValuesRepository.GetLast(discriminatorId, take, token);
@ -105,6 +114,8 @@ public class TimestampedValuesController : ControllerBase
/// <param name="approxPointsCount"></param> /// <param name="approxPointsCount"></param>
/// <param name="token"></param> /// <param name="token"></param>
[HttpGet("resampled")] [HttpGet("resampled")]
[ProducesResponseType(typeof(IEnumerable<TimestampedValuesDto>), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
public async Task<ActionResult<IEnumerable<TimestampedValuesDto>>> GetResampledData([FromRoute] Guid discriminatorId, DateTimeOffset timestampBegin, double intervalSec = 600d, int approxPointsCount = 1024, CancellationToken token = default) public async Task<ActionResult<IEnumerable<TimestampedValuesDto>>> GetResampledData([FromRoute] Guid discriminatorId, DateTimeOffset timestampBegin, double intervalSec = 600d, int approxPointsCount = 1024, CancellationToken token = default)
{ {
var result = await timestampedValuesRepository.GetResampledData(discriminatorId, timestampBegin, intervalSec, approxPointsCount, token); var result = await timestampedValuesRepository.GetResampledData(discriminatorId, timestampBegin, intervalSec, approxPointsCount, token);

View File

@ -53,6 +53,7 @@ public static class DependencyInjection
public static void AddServices(this IServiceCollection services) public static void AddServices(this IServiceCollection services)
{ {
services.AddTransient<IWitsDataService, WitsDataService>(); services.AddTransient<IWitsDataService, WitsDataService>();
services.AddTransient<ITimestampedValuesService, TimestampedValuesService>();
} }
#region Authentication #region Authentication

View File

@ -4,11 +4,11 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace DD.Persistence.Database.Entity; namespace DD.Persistence.Database.Entity;
public class ValuesIdentity public class DataScheme
{ {
[Key, Comment("Дискриминатор системы"),] [Key, Comment("Дискриминатор системы"),]
public Guid DiscriminatorId { get; set; } public Guid DiscriminatorId { get; set; }
[Comment("Идентификаторы"), Column(TypeName = "jsonb")] [Comment("Наименования полей в порядке индексации"), Column(TypeName = "jsonb")]
public string[] Identity { get; set; } = []; public string[] PropNames { get; set; } = [];
} }

View File

@ -18,5 +18,5 @@ public class TimestampedValues : ITimestampedItem
public required object[] Values { get; set; } public required object[] Values { get; set; }
[Required, ForeignKey(nameof(DiscriminatorId)), Comment("Идентификаторы")] [Required, ForeignKey(nameof(DiscriminatorId)), Comment("Идентификаторы")]
public virtual ValuesIdentity? ValuesIdentity { get; set; } public virtual DataScheme? DataScheme { get; set; }
} }

View File

@ -11,7 +11,7 @@ public class PersistenceDbContext : DbContext
{ {
public DbSet<Setpoint> Setpoint => Set<Setpoint>(); public DbSet<Setpoint> Setpoint => Set<Setpoint>();
public DbSet<ValuesIdentity> ValuesIdentities => Set<ValuesIdentity>(); public DbSet<DataScheme> DataSchemes => Set<DataScheme>();
public DbSet<TimestampedValues> TimestampedValues => Set<TimestampedValues>(); public DbSet<TimestampedValues> TimestampedValues => Set<TimestampedValues>();
@ -31,8 +31,8 @@ public class PersistenceDbContext : DbContext
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
modelBuilder.Entity<ValuesIdentity>() modelBuilder.Entity<DataScheme>()
.Property(e => e.Identity) .Property(e => e.PropNames)
.HasJsonConversion(); .HasJsonConversion();
modelBuilder.Entity<TimestampedValues>() modelBuilder.Entity<TimestampedValues>()

View File

@ -13,9 +13,9 @@ using Xunit;
namespace DD.Persistence.IntegrationTests.Controllers; namespace DD.Persistence.IntegrationTests.Controllers;
public class TimestampedValuesControllerTest : BaseIntegrationTest public class TimestampedValuesControllerTest : BaseIntegrationTest
{ {
private static readonly string SystemCacheKey = $"{typeof(ValuesIdentity).FullName}CacheKey";
private readonly ITimestampedValuesClient timestampedValuesClient; private readonly ITimestampedValuesClient timestampedValuesClient;
private readonly IMemoryCache memoryCache; private readonly IMemoryCache memoryCache;
private IEnumerable<Guid> discriminatorIds = [];
public TimestampedValuesControllerTest(WebAppFactoryFixture factory) : base(factory) public TimestampedValuesControllerTest(WebAppFactoryFixture factory) : base(factory)
{ {
@ -32,6 +32,7 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
public async Task AddRange_returns_success() public async Task AddRange_returns_success()
{ {
var discriminatorId = Guid.NewGuid(); var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
await AddRange(discriminatorId); await AddRange(discriminatorId);
} }
@ -43,6 +44,7 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
Cleanup(); Cleanup();
var discriminatorId = Guid.NewGuid(); var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
//act //act
var response = await timestampedValuesClient.Get(discriminatorId, null, null, 0, 1, CancellationToken.None); var response = await timestampedValuesClient.Get(discriminatorId, null, null, 0, 1, CancellationToken.None);
@ -59,6 +61,8 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
Cleanup(); Cleanup();
var discriminatorId = Guid.NewGuid(); var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
var timestampBegin = DateTimeOffset.UtcNow.AddDays(-1); var timestampBegin = DateTimeOffset.UtcNow.AddDays(-1);
var columnNames = new List<string>() { "A", "C" }; var columnNames = new List<string>() { "A", "C" };
var skip = 5; var skip = 5;
@ -94,6 +98,8 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
//arrange //arrange
Cleanup(); Cleanup();
var discriminatorId = Guid.NewGuid(); var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
var timestampBegin = DateTimeOffset.UtcNow.AddDays(-1); var timestampBegin = DateTimeOffset.UtcNow.AddDays(-1);
//act //act
@ -109,6 +115,8 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
//arrange //arrange
Cleanup(); Cleanup();
var discriminatorId = Guid.NewGuid(); var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
var dtos = await AddRange(discriminatorId); var dtos = await AddRange(discriminatorId);
var timestampBegin = DateTimeOffset.UtcNow.AddSeconds(-5); var timestampBegin = DateTimeOffset.UtcNow.AddSeconds(-5);
@ -130,6 +138,8 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
//arrange //arrange
Cleanup(); Cleanup();
var discriminatorId = Guid.NewGuid(); var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
var take = 1; var take = 1;
//act //act
@ -145,6 +155,8 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
//arrange //arrange
Cleanup(); Cleanup();
var discriminatorId = Guid.NewGuid(); var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
var dtos = await AddRange(discriminatorId); var dtos = await AddRange(discriminatorId);
var take = 1; var take = 1;
@ -172,7 +184,10 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
{ {
//arrange //arrange
Cleanup(); Cleanup();
var discriminatorId = Guid.NewGuid(); var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
var take = 1; var take = 1;
//act //act
@ -187,7 +202,10 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
{ {
//arrange //arrange
Cleanup(); Cleanup();
var discriminatorId = Guid.NewGuid(); var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
var dtos = await AddRange(discriminatorId); var dtos = await AddRange(discriminatorId);
var take = 1; var take = 1;
@ -215,7 +233,10 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
{ {
//arrange //arrange
Cleanup(); Cleanup();
var discriminatorId = Guid.NewGuid(); var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
var timestampBegin = DateTimeOffset.UtcNow; var timestampBegin = DateTimeOffset.UtcNow;
//act //act
@ -230,7 +251,10 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
{ {
//arrange //arrange
Cleanup(); Cleanup();
var discriminatorId = Guid.NewGuid(); var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
var count = 2048; var count = 2048;
var timestampBegin = DateTimeOffset.UtcNow; var timestampBegin = DateTimeOffset.UtcNow;
var dtos = await AddRange(discriminatorId, count); var dtos = await AddRange(discriminatorId, count);
@ -253,7 +277,9 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
{ {
//arrange //arrange
Cleanup(); Cleanup();
var discriminatorId = Guid.NewGuid(); var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
//act //act
var response = await timestampedValuesClient.Count(discriminatorId, CancellationToken.None); var response = await timestampedValuesClient.Count(discriminatorId, CancellationToken.None);
@ -267,7 +293,10 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
{ {
//arrange //arrange
Cleanup(); Cleanup();
var discriminatorId = Guid.NewGuid(); var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
var dtos = await AddRange(discriminatorId); var dtos = await AddRange(discriminatorId);
//act //act
@ -283,7 +312,9 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
{ {
//arrange //arrange
Cleanup(); Cleanup();
var discriminatorId = Guid.NewGuid(); var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
//act //act
var response = await timestampedValuesClient.GetDatesRange(discriminatorId, CancellationToken.None); var response = await timestampedValuesClient.GetDatesRange(discriminatorId, CancellationToken.None);
@ -297,7 +328,10 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
{ {
//arrange //arrange
Cleanup(); Cleanup();
var discriminatorId = Guid.NewGuid(); var discriminatorId = Guid.NewGuid();
discriminatorIds.Append(discriminatorId);
var dtos = await AddRange(discriminatorId); var dtos = await AddRange(discriminatorId);
//act //act
@ -364,8 +398,8 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
private void Cleanup() private void Cleanup()
{ {
memoryCache.Remove(SystemCacheKey); discriminatorIds = [];
dbContext.CleanupDbSet<TimestampedValues>(); dbContext.CleanupDbSet<TimestampedValues>();
dbContext.CleanupDbSet<ValuesIdentity>(); dbContext.CleanupDbSet<DataScheme>();
} }
} }

View File

@ -0,0 +1,17 @@
namespace DD.Persistence.Models;
/// <summary>
/// Схема для набора данных
/// </summary>
public class DataSchemeDto
{
/// <summary>
/// Дискриминатор
/// </summary>
public Guid DiscriminatorId { get; set; }
/// <summary>
/// Наименования полей
/// </summary>
public string[] PropNames { get; set; } = [];
}

View File

@ -1,17 +0,0 @@
namespace DD.Persistence.Models;
/// <summary>
/// Набор идентификаторов для набора данных
/// </summary>
public class ValuesIdentityDto
{
/// <summary>
/// Дискриминатор системы
/// </summary>
public Guid DiscriminatorId { get; set; }
/// <summary>
/// Идентификаторы
/// </summary>
public string[] Identity { get; set; } = [];
}

View File

@ -41,10 +41,8 @@ public static class DependencyInjection
services.AddTransient<ITimestampedValuesRepository, TimestampedValuesRepository>(); services.AddTransient<ITimestampedValuesRepository, TimestampedValuesRepository>();
services.AddTransient<ITechMessagesRepository, TechMessagesRepository>(); services.AddTransient<ITechMessagesRepository, TechMessagesRepository>();
services.AddTransient<IParameterRepository, ParameterRepository>(); services.AddTransient<IParameterRepository, ParameterRepository>();
services.AddTransient<IRelatedDataRepository<DataSourceSystemDto>, services.AddTransient<IDataSourceSystemRepository, DataSourceSystemCachedRepository>();
RelatedDataCachedRepository<DataSourceSystemDto, DataSourceSystem>>(); services.AddTransient<IDataSchemeRepository, DataSchemeCachedRepository>();
services.AddTransient<IRelatedDataRepository<ValuesIdentityDto>,
RelatedDataCachedRepository<ValuesIdentityDto, ValuesIdentity>>();
return services; return services;
} }

View File

@ -3,6 +3,7 @@ using DD.Persistence.Models.Requests;
using DD.Persistence.Models.Common; using DD.Persistence.Models.Common;
using DD.Persistence.ModelsAbstractions; using DD.Persistence.ModelsAbstractions;
using DD.Persistence.Database.EntityAbstractions; using DD.Persistence.Database.EntityAbstractions;
using DD.Persistence.Extensions;
namespace DD.Persistence.Repository; namespace DD.Persistence.Repository;

View File

@ -0,0 +1,34 @@
using DD.Persistence.Database.Entity;
using DD.Persistence.Models;
using DD.Persistence.Repositories;
using Mapster;
using Microsoft.EntityFrameworkCore;
namespace DD.Persistence.Repository.Repositories;
public class DataSchemeRepository : IDataSchemeRepository
{
protected DbContext db;
public DataSchemeRepository(DbContext db)
{
this.db = db;
}
protected virtual IQueryable<DataScheme> GetQueryReadOnly() => db.Set<DataScheme>();
public virtual async Task Add(DataSchemeDto dataSourceSystemDto, CancellationToken token)
{
var entity = dataSourceSystemDto.Adapt<DataScheme>();
await db.Set<DataScheme>().AddAsync(entity, token);
await db.SaveChangesAsync(token);
}
public virtual async Task<DataSchemeDto?> GetByDiscriminator(Guid discriminatorId, CancellationToken token)
{
var query = GetQueryReadOnly()
.Where(e => e.DiscriminatorId == discriminatorId);
var entity = await query.ToArrayAsync();
var dto = entity.Select(e => e.Adapt<DataSchemeDto>()).FirstOrDefault();
return dto;
}
}

View File

@ -0,0 +1,33 @@
using DD.Persistence.Database.Entity;
using DD.Persistence.Models;
using DD.Persistence.Repositories;
using Mapster;
using Microsoft.EntityFrameworkCore;
namespace DD.Persistence.Repository.Repositories;
public class DataSourceSystemRepository : IDataSourceSystemRepository
{
protected DbContext db;
public DataSourceSystemRepository(DbContext db)
{
this.db = db;
}
protected virtual IQueryable<DataSourceSystem> GetQueryReadOnly() => db.Set<DataSourceSystem>();
public virtual async Task Add(DataSourceSystemDto dataSourceSystemDto, CancellationToken token)
{
var entity = dataSourceSystemDto.Adapt<DataSourceSystem>();
await db.Set<DataSourceSystem>().AddAsync(entity, token);
await db.SaveChangesAsync(token);
}
public virtual async Task<IEnumerable<DataSourceSystemDto>> Get(CancellationToken token)
{
var query = GetQueryReadOnly();
var entities = await query.ToArrayAsync(token);
var dtos = entities.Select(e => e.Adapt<DataSourceSystemDto>());
return dtos;
}
}

View File

@ -1,33 +0,0 @@
using DD.Persistence.Repositories;
using Mapster;
using Microsoft.EntityFrameworkCore;
namespace DD.Persistence.Repository.Repositories;
public class RelatedDataRepository<TDto, TEntity> : IRelatedDataRepository<TDto>
where TDto : class, new()
where TEntity : class, new()
{
protected DbContext db;
public RelatedDataRepository(DbContext db)
{
this.db = db;
}
protected virtual IQueryable<TEntity> GetQueryReadOnly() => db.Set<TEntity>();
public virtual async Task Add(TDto dataSourceSystemDto, CancellationToken token)
{
var entity = dataSourceSystemDto.Adapt<TEntity>();
await db.Set<TEntity>().AddAsync(entity, token);
await db.SaveChangesAsync(token);
}
public virtual async Task<IEnumerable<TDto>> Get(CancellationToken token)
{
var query = GetQueryReadOnly();
var entities = await query.ToArrayAsync(token);
var dtos = entities.Select(e => e.Adapt<TDto>());
return dtos;
}
}

View File

@ -1,4 +1,5 @@
using DD.Persistence.Database.Entity; using DD.Persistence.Database.Entity;
using DD.Persistence.Extensions;
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Models.Common; using DD.Persistence.Models.Common;
using DD.Persistence.Models.Requests; using DD.Persistence.Models.Requests;
@ -10,10 +11,10 @@ namespace DD.Persistence.Repository.Repositories
{ {
public class TechMessagesRepository : ITechMessagesRepository public class TechMessagesRepository : ITechMessagesRepository
{ {
private readonly IRelatedDataRepository<DataSourceSystemDto> sourceSystemRepository; private readonly IDataSourceSystemRepository sourceSystemRepository;
private DbContext db; private DbContext db;
public TechMessagesRepository(DbContext db, IRelatedDataRepository<DataSourceSystemDto> sourceSystemRepository) public TechMessagesRepository(DbContext db, IDataSourceSystemRepository sourceSystemRepository)
{ {
this.db = db; this.db = db;
this.sourceSystemRepository = sourceSystemRepository; this.sourceSystemRepository = sourceSystemRepository;

View File

@ -2,32 +2,25 @@
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Models.Common; using DD.Persistence.Models.Common;
using DD.Persistence.Repositories; using DD.Persistence.Repositories;
using DD.Persistence.Repository.Extensions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace DD.Persistence.Repository.Repositories; namespace DD.Persistence.Repository.Repositories;
public class TimestampedValuesRepository : ITimestampedValuesRepository public class TimestampedValuesRepository : ITimestampedValuesRepository
{ {
private readonly DbContext db; private readonly DbContext db;
private readonly IRelatedDataRepository<ValuesIdentityDto> relatedDataRepository;
public TimestampedValuesRepository(DbContext db, IRelatedDataRepository<ValuesIdentityDto> relatedDataRepository) public TimestampedValuesRepository(DbContext db)
{ {
this.db = db; this.db = db;
this.relatedDataRepository = relatedDataRepository;
} }
protected virtual IQueryable<TimestampedValues> GetQueryReadOnly() => this.db.Set<TimestampedValues>() protected virtual IQueryable<TimestampedValues> GetQueryReadOnly() => this.db.Set<TimestampedValues>();
.Include(e => e.ValuesIdentity);
public async virtual Task<int> AddRange(Guid discriminatorId, IEnumerable<TimestampedValuesDto> dtos, CancellationToken token) public async virtual Task<int> AddRange(Guid discriminatorId, IEnumerable<TimestampedValuesDto> dtos, CancellationToken token)
{ {
var timestampedValuesEntities = new List<TimestampedValues>(); var timestampedValuesEntities = new List<TimestampedValues>();
foreach (var dto in dtos) foreach (var dto in dtos)
{ {
var keys = dto.Values.Keys.ToArray();
await CreateValuesIdentityIfNotExist(discriminatorId, keys, token);
var timestampedValuesEntity = new TimestampedValues() var timestampedValuesEntity = new TimestampedValues()
{ {
DiscriminatorId = discriminatorId, DiscriminatorId = discriminatorId,
@ -44,7 +37,7 @@ public class TimestampedValuesRepository : ITimestampedValuesRepository
return result; return result;
} }
public async virtual Task<IEnumerable<TimestampedValuesDto>> Get(Guid discriminatorId, DateTimeOffset? timestampBegin, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token) public async virtual Task<IEnumerable<Tuple<DateTimeOffset, object[]>>> Get(Guid discriminatorId, DateTimeOffset? timestampBegin, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token)
{ {
var query = GetQueryReadOnly() var query = GetQueryReadOnly()
.Where(entity => entity.DiscriminatorId == discriminatorId); .Where(entity => entity.DiscriminatorId == discriminatorId);
@ -59,68 +52,79 @@ public class TimestampedValuesRepository : ITimestampedValuesRepository
.OrderBy(item => item.Timestamp) .OrderBy(item => item.Timestamp)
.Skip(skip) .Skip(skip)
.Take(take); .Take(take);
var data = await Materialize(discriminatorId, query, token); var entities = await query.ToArrayAsync(token);
// Фильтрация по запрашиваемым полям var result = entities.Select(e => Tuple.Create(
if (!columnNames.IsNullOrEmpty()) e.Timestamp,
{ e.Values
data = ReduceSetColumnsByNames(data, columnNames!); ));
}
return data; return result;
} }
public async virtual Task<IEnumerable<TimestampedValuesDto>> GetFirst(Guid discriminatorId, int takeCount, CancellationToken token) public async virtual Task<IEnumerable<Tuple<DateTimeOffset, object[]>>> GetFirst(Guid discriminatorId, int takeCount, CancellationToken token)
{ {
var query = GetQueryReadOnly() var query = GetQueryReadOnly()
.OrderBy(e => e.Timestamp) .OrderBy(e => e.Timestamp)
.Take(takeCount); .Take(takeCount);
var entities = await query.ToArrayAsync(token);
var dtos = await Materialize(discriminatorId, query, token); var result = entities.Select(e => Tuple.Create(
e.Timestamp,
e.Values
));
return dtos; return result;
} }
public async virtual Task<IEnumerable<TimestampedValuesDto>> GetLast(Guid discriminatorId, int takeCount, CancellationToken token) public async virtual Task<IEnumerable<Tuple<DateTimeOffset, object[]>>> GetLast(Guid discriminatorId, int takeCount, CancellationToken token)
{ {
var query = GetQueryReadOnly() var query = GetQueryReadOnly()
.OrderByDescending(e => e.Timestamp) .OrderByDescending(e => e.Timestamp)
.Take(takeCount); .Take(takeCount);
var entities = await query.ToArrayAsync(token);
var dtos = await Materialize(discriminatorId, query, token); var result = entities.Select(e => Tuple.Create(
e.Timestamp,
e.Values
));
return dtos; return result;
} }
public async virtual Task<IEnumerable<TimestampedValuesDto>> GetResampledData( public async virtual Task<IEnumerable<Tuple<DateTimeOffset, object[]>>> GetResampledData(
Guid discriminatorId, Guid discriminatorId,
DateTimeOffset dateBegin, DateTimeOffset dateBegin,
double intervalSec = 600d, double intervalSec = 600d,
int approxPointsCount = 1024, int approxPointsCount = 1024,
CancellationToken token = default) CancellationToken token = default)
{ {
var dtos = await GetGtDate(discriminatorId, dateBegin, token); var result = await GetGtDate(discriminatorId, dateBegin, token);
var dateEnd = dateBegin.AddSeconds(intervalSec); var dateEnd = dateBegin.AddSeconds(intervalSec);
dtos = dtos result = result
.Where(i => i.Timestamp <= dateEnd); .Where(i => i.Item1 <= dateEnd);
var ratio = dtos.Count() / approxPointsCount; var ratio = result.Count() / approxPointsCount;
if (ratio > 1) if (ratio > 1)
dtos = dtos result = result
.Where((_, index) => index % ratio == 0); .Where((_, index) => index % ratio == 0);
return dtos; return result;
} }
public async virtual Task<IEnumerable<TimestampedValuesDto>> GetGtDate(Guid discriminatorId, DateTimeOffset timestampBegin, CancellationToken token) public async virtual Task<IEnumerable<Tuple<DateTimeOffset, object[]>>> GetGtDate(Guid discriminatorId, DateTimeOffset timestampBegin, CancellationToken token)
{ {
var query = GetQueryReadOnly() var query = GetQueryReadOnly()
.Where(e => e.Timestamp > timestampBegin); .Where(e => e.Timestamp > timestampBegin);
var entities = await query.ToArrayAsync(token);
var dtos = await Materialize(discriminatorId, query, token); var result = entities.Select(e => Tuple.Create(
e.Timestamp,
e.Values
));
return dtos; return result;
} }
public async virtual Task<DatesRangeDto?> GetDatesRange(Guid discriminatorId, CancellationToken token) public async virtual Task<DatesRangeDto?> GetDatesRange(Guid discriminatorId, CancellationToken token)
@ -156,37 +160,12 @@ public class TimestampedValuesRepository : ITimestampedValuesRepository
return query.CountAsync(token); return query.CountAsync(token);
} }
private async Task<IEnumerable<TimestampedValuesDto>> Materialize(Guid discriminatorId, IQueryable<TimestampedValues> query, CancellationToken token) /// <summary>
{ /// Применить фильтр по дате
var valuesIdentities = await relatedDataRepository.Get(token); /// </summary>
var valuesIdentity = valuesIdentities? /// <param name="query"></param>
.FirstOrDefault(e => e.DiscriminatorId == discriminatorId); /// <param name="timestampBegin"></param>
if (valuesIdentity == null) /// <returns></returns>
return [];
var entities = await query.ToArrayAsync(token);
var dtos = entities.Select(entity =>
{
var dto = new TimestampedValuesDto()
{
Timestamp = entity.Timestamp.ToUniversalTime()
};
for (var i = 0; i < valuesIdentity.Identity.Count(); i++)
{
var key = valuesIdentity.Identity[i];
var value = entity.Values[i];
dto.Values.Add(key, value);
}
return dto;
});
return dtos;
}
private IQueryable<TimestampedValues> ApplyGeTimestamp(IQueryable<TimestampedValues> query, DateTimeOffset timestampBegin) private IQueryable<TimestampedValues> ApplyGeTimestamp(IQueryable<TimestampedValues> query, DateTimeOffset timestampBegin)
{ {
var geTimestampUtc = timestampBegin.ToUniversalTime(); var geTimestampUtc = timestampBegin.ToUniversalTime();
@ -196,46 +175,4 @@ public class TimestampedValuesRepository : ITimestampedValuesRepository
return result; return result;
} }
private IEnumerable<TimestampedValuesDto> ReduceSetColumnsByNames(IEnumerable<TimestampedValuesDto> dtos, IEnumerable<string> columnNames)
{
var result = dtos.Select(dto =>
{
var reducedValues = dto.Values
.Where(v => columnNames.Contains(v.Key))
.ToDictionary();
dto.Values = reducedValues;
return dto;
});
return result;
}
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 is 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}]");
}
}
} }

View File

@ -0,0 +1,30 @@
using DD.Persistence.Models;
using DD.Persistence.Repository.Repositories;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
namespace DD.Persistence.Repository.RepositoriesCached;
public class DataSchemeCachedRepository : DataSchemeRepository
{
private readonly IMemoryCache memoryCache;
public DataSchemeCachedRepository(DbContext db, IMemoryCache memoryCache) : base(db)
{
this.memoryCache = memoryCache;
}
public override async Task Add(DataSchemeDto dataSourceSystemDto, CancellationToken token)
{
await base.Add(dataSourceSystemDto, token);
memoryCache.Set(dataSourceSystemDto.DiscriminatorId, dataSourceSystemDto);
}
public override async Task<DataSchemeDto?> GetByDiscriminator(Guid discriminatorId, CancellationToken token)
{
var result = memoryCache.Get<DataSchemeDto>(discriminatorId)
?? await base.GetByDiscriminator(discriminatorId, token);
return result;
}
}

View File

@ -1,27 +1,27 @@
using DD.Persistence.Repository.Repositories; using DD.Persistence.Database.Entity;
using DD.Persistence.Models;
using DD.Persistence.Repository.Repositories;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
namespace DD.Persistence.Repository.RepositoriesCached; namespace DD.Persistence.Repository.RepositoriesCached;
public class RelatedDataCachedRepository<TDto, TEntity> : RelatedDataRepository<TDto, TEntity> public class DataSourceSystemCachedRepository : DataSourceSystemRepository
where TEntity : class, new()
where TDto : class, new()
{ {
private static readonly string SystemCacheKey = $"{typeof(TEntity).FullName}CacheKey"; private static readonly string SystemCacheKey = $"{typeof(DataSourceSystem).FullName}CacheKey";
private readonly IMemoryCache memoryCache; private readonly IMemoryCache memoryCache;
private readonly TimeSpan? AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(60); private readonly TimeSpan? AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(60);
public RelatedDataCachedRepository(DbContext db, IMemoryCache memoryCache) : base(db) public DataSourceSystemCachedRepository(DbContext db, IMemoryCache memoryCache) : base(db)
{ {
this.memoryCache = memoryCache; this.memoryCache = memoryCache;
} }
public override async Task Add(TDto dataSourceSystemDto, CancellationToken token) public override async Task Add(DataSourceSystemDto dataSourceSystemDto, CancellationToken token)
{ {
await base.Add(dataSourceSystemDto, token); await base.Add(dataSourceSystemDto, token);
memoryCache.Remove(SystemCacheKey); memoryCache.Remove(SystemCacheKey);
} }
public override async Task<IEnumerable<TDto>> Get(CancellationToken token) public override async Task<IEnumerable<DataSourceSystemDto>> Get(CancellationToken token)
{ {
var systems = await memoryCache.GetOrCreateAsync(SystemCacheKey, async (cacheEntry) => var systems = await memoryCache.GetOrCreateAsync(SystemCacheKey, async (cacheEntry) =>
{ {

View File

@ -12,7 +12,7 @@
// private const int CacheItemsCount = 3600; // private const int CacheItemsCount = 3600;
// public TimestampedValuesCachedRepository(DbContext db, IRelatedDataRepository<ValuesIdentityDto> relatedDataRepository) : base(db, relatedDataRepository) // public TimestampedValuesCachedRepository(DbContext db, IDataSourceSystemRepository<ValuesIdentityDto> relatedDataRepository) : base(db, relatedDataRepository)
// { // {
// //Task.Run(async () => // //Task.Run(async () =>
// //{ // //{

View File

@ -2,7 +2,7 @@
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
namespace DD.Persistence; namespace DD.Persistence.Extensions;
public static class EFExtensions public static class EFExtensions
{ {
struct TypeAccessor struct TypeAccessor
@ -23,7 +23,7 @@ public static class EFExtensions
private static ConcurrentDictionary<Type, Dictionary<string, TypeAccessor>> TypePropSelectors { get; set; } = new(); private static ConcurrentDictionary<Type, Dictionary<string, TypeAccessor>> TypePropSelectors { get; set; } = new();
private static MethodInfo GetExtOrderMethod(string methodName) private static MethodInfo GetExtOrderMethod(string methodName)
=> typeof(System.Linq.Queryable) => typeof(Queryable)
.GetMethods() .GetMethods()
.Where(m => m.Name == methodName && .Where(m => m.Name == methodName &&
m.IsGenericMethodDefinition && m.IsGenericMethodDefinition &&

View File

@ -1,4 +1,4 @@
namespace DD.Persistence.Repository.Extensions; namespace DD.Persistence.Extensions;
public static class IEnumerableExtensions public static class IEnumerableExtensions
{ {

View File

@ -0,0 +1,25 @@
using DD.Persistence.Models;
namespace DD.Persistence.Repositories;
/// <summary>
/// Репозиторий для работы со схемами наборов данных
/// </summary>
public interface IDataSchemeRepository
{
/// <summary>
/// Добавить схему
/// </summary>
/// <param name="dataSourceSystemDto"></param>
/// <param name="token"></param>
/// <returns></returns>
Task Add(DataSchemeDto dataSourceSystemDto, CancellationToken token);
/// <summary>
/// Вычитать схему
/// </summary>
/// <param name="discriminatorId">Дискриминатор системы</param>
/// <param name="token"></param>
/// <returns></returns>
Task<DataSchemeDto?> GetByDiscriminator(Guid discriminatorId, CancellationToken token);
}

View File

@ -0,0 +1,23 @@
using DD.Persistence.Models;
namespace DD.Persistence.Repositories;
/// <summary>
/// Репозиторий для работы с системами - источниками данных
/// </summary>
public interface IDataSourceSystemRepository
{
/// <summary>
/// Добавить систему - источник данных
/// </summary>
/// <param name="dataSourceSystemDto"></param>
/// <param name="token"></param>
/// <returns></returns>
public Task Add(DataSourceSystemDto dataSourceSystemDto, CancellationToken token);
/// <summary>
/// Получить список систем - источников данных
/// </summary>
/// <returns></returns>
public Task<IEnumerable<DataSourceSystemDto>> Get(CancellationToken token);
}

View File

@ -1,23 +0,0 @@
namespace DD.Persistence.Repositories;
/// <summary>
/// Интерфейс по работе с простой структурой данных, подразумевающей наличие связи с более сложной
/// В контексте TechMessagesRepository это системы - источники данных
/// В контексте TimestampedValuesRepository это идентификационные наборы (ключи для значений в соответствии с индексами в хранимых массивах)
/// </summary>
public interface IRelatedDataRepository<TDto>
{
/// <summary>
/// Добавить данные
/// </summary>
/// <param name="dataSourceSystemDto"></param>
/// <param name="token"></param>
/// <returns></returns>
public Task Add(TDto dataSourceSystemDto, CancellationToken token);
/// <summary>
/// Получить список данных
/// </summary>
/// <returns></returns>
public Task<IEnumerable<TDto>> Get(CancellationToken token);
}

View File

@ -4,14 +4,14 @@ using DD.Persistence.RepositoriesAbstractions;
namespace DD.Persistence.Repositories; namespace DD.Persistence.Repositories;
/// <summary> /// <summary>
/// Интерфейс по работе с временными данными /// Репозиторий для работы с временными данными
/// </summary> /// </summary>
public interface ITimestampedValuesRepository : ISyncRepository, ITimeSeriesBaseRepository public interface ITimestampedValuesRepository : ISyncRepository, ITimeSeriesBaseRepository
{ {
/// <summary> /// <summary>
/// Добавление записей /// Добавление записей
/// </summary> /// </summary>
/// <param name="idDiscriminator"></param> /// <param name="idDiscriminator">Дискриминатор (идентификатор) набора</param>
/// <param name="dtos"></param> /// <param name="dtos"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
@ -35,7 +35,7 @@ public interface ITimestampedValuesRepository : ISyncRepository, ITimeSeriesBase
/// <param name="take"></param> /// <param name="take"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<TimestampedValuesDto>> Get(Guid idDiscriminator, DateTimeOffset? geTimestamp, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token); Task<IEnumerable<Tuple<DateTimeOffset, object[]>>> Get(Guid idDiscriminator, DateTimeOffset? geTimestamp, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token);
/// <summary> /// <summary>
/// Получение данных с начала /// Получение данных с начала
@ -44,7 +44,7 @@ public interface ITimestampedValuesRepository : ISyncRepository, ITimeSeriesBase
/// <param name="takeCount">Количество</param> /// <param name="takeCount">Количество</param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<TimestampedValuesDto>> GetFirst(Guid discriminatorId, int takeCount, CancellationToken token); Task<IEnumerable<Tuple<DateTimeOffset, object[]>>> GetFirst(Guid discriminatorId, int takeCount, CancellationToken token);
/// <summary> /// <summary>
/// Получение данных с конца /// Получение данных с конца
@ -53,5 +53,5 @@ public interface ITimestampedValuesRepository : ISyncRepository, ITimeSeriesBase
/// <param name="takeCount">Количество</param> /// <param name="takeCount">Количество</param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<TimestampedValuesDto>> GetLast(Guid discriminatorId, int takeCount, CancellationToken token); Task<IEnumerable<Tuple<DateTimeOffset, object[]>>> GetLast(Guid discriminatorId, int takeCount, CancellationToken token);
} }

View File

@ -6,7 +6,7 @@ namespace DD.Persistence.RepositoriesAbstractions;
/// <summary> /// <summary>
/// Интерфейс по работе с данными /// Интерфейс по работе с данными
/// </summary> /// </summary>
public interface ISyncRepository public interface ISyncRepository // ToDo: исчерпывающая абстракция
{ {
/// <summary> /// <summary>
/// Получить данные, начиная с определенной даты /// Получить данные, начиная с определенной даты
@ -15,7 +15,7 @@ public interface ISyncRepository
/// <param name="dateBegin">дата начала</param> /// <param name="dateBegin">дата начала</param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<TimestampedValuesDto>> GetGtDate(Guid discriminatorId, DateTimeOffset dateBegin, CancellationToken token); Task<IEnumerable<Tuple<DateTimeOffset, object[]>>> GetGtDate(Guid discriminatorId, DateTimeOffset dateBegin, CancellationToken token);
/// <summary> /// <summary>

View File

@ -5,7 +5,7 @@ namespace DD.Persistence.RepositoriesAbstractions;
/// <summary> /// <summary>
/// Интерфейс по работе с прореженными данными /// Интерфейс по работе с прореженными данными
/// </summary> /// </summary>
public interface ITimeSeriesBaseRepository public interface ITimeSeriesBaseRepository // ToDo: исчерпывающая абстракция
{ {
/// <summary> /// <summary>
/// Получить список объектов с прореживанием /// Получить список объектов с прореживанием
@ -16,7 +16,7 @@ public interface ITimeSeriesBaseRepository
/// <param name="approxPointsCount"></param> /// <param name="approxPointsCount"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<TimestampedValuesDto>> GetResampledData( Task<IEnumerable<Tuple<DateTimeOffset, object[]>>> GetResampledData(
Guid discriminatorId, Guid discriminatorId,
DateTimeOffset dateBegin, DateTimeOffset dateBegin,
double intervalSec = 600d, double intervalSec = 600d,

View File

@ -0,0 +1,85 @@
using DD.Persistence.Models;
using DD.Persistence.Models.Common;
namespace DD.Persistence.Services.Interfaces;
/// <summary>
/// Сервис для работы с временными данными
/// </summary>
public interface ITimestampedValuesService
{
/// <summary>
/// Добавление записей
/// </summary>
/// <param name="discriminatorId"></param>
/// <param name="dtos"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> AddRange(Guid discriminatorId, IEnumerable<TimestampedValuesDto> dtos, CancellationToken token);
/// <summary>
/// Количество записей по указанному набору в БД. Для пагинации
/// </summary>
/// <param name="discriminatorId">Дискриминатор (идентификатор) набора</param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> Count(Guid discriminatorId, CancellationToken token);
/// <summary>
/// Получение данных с фильтрацией. Значение фильтра null - отключен
/// </summary>
/// <param name="discriminatorId">Дискриминатор (идентификатор) набора</param>
/// <param name="geTimestamp">Фильтр позднее даты</param>
/// <param name="columnNames">Фильтр свойств набора. Можно запросить только некоторые свойства из набора</param>
/// <param name="skip"></param>
/// <param name="take"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<TimestampedValuesDto>> Get(Guid discriminatorId, DateTimeOffset? geTimestamp, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token);
/// <summary>
/// Получение данных с начала
/// </summary>
/// <param name="discriminatorId">Дискриминатор (идентификатор) набора</param>
/// <param name="takeCount">Количество</param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<TimestampedValuesDto>> GetFirst(Guid discriminatorId, int takeCount, CancellationToken token);
/// <summary>
/// Получить данные, начиная с определенной даты
/// </summary>
/// <param name="discriminatorId"></param>
/// <param name="timestampBegin">дата начала</param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<TimestampedValuesDto>> GetGtDate(Guid discriminatorId, DateTimeOffset timestampBegin, CancellationToken token);
/// <summary>
/// Получение данных с конца
/// </summary>
/// <param name="discriminatorId">Дискриминатор (идентификатор) набора</param>
/// <param name="takeCount">Количество</param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<TimestampedValuesDto>> GetLast(Guid discriminatorId, int takeCount, CancellationToken token);
/// <summary>
/// Получить список объектов с прореживанием
/// </summary>
/// <param name="discriminatorId"></param>
/// <param name="timestampBegin">дата начала</param>
/// <param name="intervalSec"></param>
/// <param name="approxPointsCount"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<TimestampedValuesDto>> GetResampledData(Guid discriminatorId, DateTimeOffset timestampBegin, double intervalSec = 600, int approxPointsCount = 1024, CancellationToken token = default);
/// <summary>
/// Получить диапазон дат, для которых есть данные
/// </summary>
/// <param name="discriminatorId"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<DatesRangeDto?> GetDatesRange(Guid discriminatorId, CancellationToken token);
}

View File

@ -0,0 +1,201 @@
using DD.Persistence.Extensions;
using DD.Persistence.Models;
using DD.Persistence.Models.Common;
using DD.Persistence.Repositories;
using DD.Persistence.Services.Interfaces;
namespace DD.Persistence.Services;
/// <inheritdoc/>
public class TimestampedValuesService : ITimestampedValuesService
{
private readonly ITimestampedValuesRepository timestampedValuesRepository;
private readonly IDataSchemeRepository dataSchemeRepository;
/// <inheritdoc/>
public TimestampedValuesService(ITimestampedValuesRepository timestampedValuesRepository, IDataSchemeRepository relatedDataRepository)
{
this.timestampedValuesRepository = timestampedValuesRepository;
this.dataSchemeRepository = relatedDataRepository;
}
/// <inheritdoc/>
public async Task<int> AddRange(Guid discriminatorId, IEnumerable<TimestampedValuesDto> dtos, CancellationToken token)
{
foreach (var dto in dtos)
{
var keys = dto.Values.Keys.ToArray();
await CreateSystemSpecificationIfNotExist(discriminatorId, keys, token);
}
var result = await timestampedValuesRepository.AddRange(discriminatorId, dtos, token);
return result;
}
/// <inheritdoc/>
public async Task<IEnumerable<TimestampedValuesDto>> Get(Guid discriminatorId, DateTimeOffset? geTimestamp, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token)
{
var result = await timestampedValuesRepository.Get(discriminatorId, geTimestamp, columnNames, skip, take, token);
var dtos = await Materialize(discriminatorId, result, token);
if (!columnNames.IsNullOrEmpty())
{
dtos = ReduceSetColumnsByNames(dtos, columnNames!);
}
return dtos;
}
/// <inheritdoc/>
public async Task<IEnumerable<TimestampedValuesDto>> GetFirst(Guid discriminatorId, int takeCount, CancellationToken token)
{
var result = await timestampedValuesRepository.GetFirst(discriminatorId, takeCount, token);
var dtos = await Materialize(discriminatorId, result, token);
return dtos;
}
/// <inheritdoc/>
public async Task<IEnumerable<TimestampedValuesDto>> GetLast(Guid discriminatorId, int takeCount, CancellationToken token)
{
var result = await timestampedValuesRepository.GetLast(discriminatorId, takeCount, token);
var dtos = await Materialize(discriminatorId, result, token);
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);
var dtos = await Materialize(discriminatorId, result, token);
return dtos;
}
/// <inheritdoc/>
public async Task<IEnumerable<TimestampedValuesDto>> GetGtDate(Guid discriminatorId, DateTimeOffset beginTimestamp, CancellationToken token)
{
var result = await timestampedValuesRepository.GetGtDate(discriminatorId, beginTimestamp, token);
var dtos = await Materialize(discriminatorId, result, token);
return dtos;
}
/// <inheritdoc/>
public async Task<int> Count(Guid discriminatorId, CancellationToken token)
{
var result = await timestampedValuesRepository.Count(discriminatorId, token);
return result;
}
/// <inheritdoc/>
public async virtual Task<DatesRangeDto?> GetDatesRange(Guid discriminatorId, CancellationToken token)
{
var result = await timestampedValuesRepository.GetDatesRange(discriminatorId, token);
return result;
}
/// <summary>
/// Преобразовать результат запроса в набор dto
/// </summary>
/// <param name="discriminatorId"></param>
/// <param name="queryResult"></param>
/// <param name="token"></param>
/// <returns></returns>
private async Task<IEnumerable<TimestampedValuesDto>> Materialize(Guid discriminatorId, IEnumerable<Tuple<DateTimeOffset, object[]>> queryResult, CancellationToken token)
{
var systemSpecification = await dataSchemeRepository.GetByDiscriminator(discriminatorId, token);
if (systemSpecification is null)
{
return [];
}
var dtos = queryResult.Select(entity =>
{
var dto = new TimestampedValuesDto()
{
Timestamp = entity.Item1.ToUniversalTime()
};
var identity = systemSpecification!.PropNames;
for (var i = 0; i < identity.Count(); i++)
{
var key = identity[i];
var value = entity.Item2[i];
dto.Values.Add(key, value);
}
return dto;
});
return dtos;
}
/// <summary>
/// Создать спецификацию, при отсутствии таковой
/// </summary>
/// <param name="discriminatorId">Дискриминатор системы</param>
/// <param name="fieldNames">Набор наименований полей</param>
/// <param name="token"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException">Некорректный набор наименований полей</exception>
private async Task CreateSystemSpecificationIfNotExist(Guid discriminatorId, string[] fieldNames, CancellationToken token)
{
var systemSpecification = await dataSchemeRepository.GetByDiscriminator(discriminatorId, token);
if (systemSpecification is null)
{
systemSpecification = new DataSchemeDto()
{
DiscriminatorId = discriminatorId,
PropNames = fieldNames
};
await dataSchemeRepository.Add(systemSpecification, token);
return;
}
if (!systemSpecification.PropNames.SequenceEqual(fieldNames))
{
var expectedFieldNames = string.Join(", ", systemSpecification.PropNames);
var actualFieldNames = string.Join(", ", fieldNames);
throw new InvalidOperationException($"Для системы {discriminatorId.ToString()} " +
$"характерен набор данных: [{expectedFieldNames}], однако был передан набор: [{actualFieldNames}]");
}
}
/// <summary>
/// Отсеить лишние поля в соответствии с заданным фильтром
/// </summary>
/// <param name="dtos"></param>
/// <param name="fieldNames">Поля, которые необходимо оставить</param>
/// <returns></returns>
private IEnumerable<TimestampedValuesDto> ReduceSetColumnsByNames(IEnumerable<TimestampedValuesDto> dtos, IEnumerable<string> fieldNames)
{
var result = dtos.Select(dto =>
{
var reducedValues = dto.Values
.Where(v => fieldNames.Contains(v.Key))
.ToDictionary();
dto.Values = reducedValues;
return dto;
});
return result;
}
}