From b75714c8354e65fba57231cedd27d3216201fca8 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Fri, 22 Nov 2024 17:52:15 +0500 Subject: [PATCH 1/5] Add TimestampedSetRepository --- .../Controllers/TimestampedSetController.cs | 63 +++++ .../PersistenceDbContext.cs | 12 +- Persistence.Database/EFExtensions.cs | 48 ++++ Persistence.Database/Entity/TimestampedSet.cs | 11 + Persistence.Database/IPersistenceDbContext.cs | 5 + .../Clients/ITimestampedSetClient.cs | 23 ++ .../TimestampedSetControllerTest.cs | 219 ++++++++++++++++++ .../WebAppFactoryFixture.cs | 3 + Persistence.Repository/DependencyInjection.cs | 1 + .../Repositories/TimestampedSetRepository.cs | 110 +++++++++ Persistence/Models/TimestampedSetDto.cs | 8 + .../Repositories/ITimestampedSetRepository.cs | 11 + 12 files changed, 511 insertions(+), 3 deletions(-) create mode 100644 Persistence.API/Controllers/TimestampedSetController.cs create mode 100644 Persistence.Database/EFExtensions.cs create mode 100644 Persistence.Database/Entity/TimestampedSet.cs create mode 100644 Persistence.IntegrationTests/Clients/ITimestampedSetClient.cs create mode 100644 Persistence.IntegrationTests/Controllers/TimestampedSetControllerTest.cs create mode 100644 Persistence.Repository/Repositories/TimestampedSetRepository.cs create mode 100644 Persistence/Models/TimestampedSetDto.cs create mode 100644 Persistence/Repositories/ITimestampedSetRepository.cs diff --git a/Persistence.API/Controllers/TimestampedSetController.cs b/Persistence.API/Controllers/TimestampedSetController.cs new file mode 100644 index 0000000..3d7ce63 --- /dev/null +++ b/Persistence.API/Controllers/TimestampedSetController.cs @@ -0,0 +1,63 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Persistence.Models; +using Persistence.Repositories; +using Persistence.Repository.Repositories; +using System.Net; + +namespace Persistence.API.Controllers; + +[ApiController] +[Authorize] +[Route("api/[controller]/{idDiscriminator}")] +public class TimestampedSetController : ControllerBase +{ + private readonly ITimestampedSetRepository repository; + + public TimestampedSetController(ITimestampedSetRepository repository) + { + this.repository = repository; + } + + [HttpPost] + [ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)] + public async Task InsertRange([FromRoute]Guid idDiscriminator, [FromBody]IEnumerable sets, CancellationToken token) + { + var result = await repository.InsertRange(idDiscriminator, sets, token); + return Ok(result); + } + + [HttpGet] + [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)] + public async Task Get(Guid idDiscriminator, DateTimeOffset? geTimestamp, [FromQuery]IEnumerable? props, int skip, int take, CancellationToken token) + { + var result = await repository.Get(idDiscriminator, geTimestamp, props, skip, take, token); + return Ok(result); + } + + [HttpGet("last")] + [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)] + public async Task GetLast(Guid idDiscriminator, [FromQuery]IEnumerable? props, int take, CancellationToken token) + { + var result = await repository.GetLast(idDiscriminator, props, take, token); + return Ok(result); + } + + [HttpGet("datesRange")] + [ProducesResponseType(typeof(DatesRangeDto), (int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.NoContent)] + public async Task GetDatesRange(Guid idDiscriminator, CancellationToken token) + { + var result = await repository.GetDatesRange(idDiscriminator, token); + return Ok(result); + } + + [HttpGet("count")] + [ProducesResponseType(typeof(DatesRangeDto), (int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.NoContent)] + public async Task Count(Guid idDiscriminator, CancellationToken token) + { + var result = await repository.Count(idDiscriminator, token); + return Ok(result); + } +} diff --git a/Persistence.Database.Postgres/PersistenceDbContext.cs b/Persistence.Database.Postgres/PersistenceDbContext.cs index f68be73..22462ed 100644 --- a/Persistence.Database.Postgres/PersistenceDbContext.cs +++ b/Persistence.Database.Postgres/PersistenceDbContext.cs @@ -1,4 +1,6 @@ using Microsoft.EntityFrameworkCore; +using Npgsql; +using Persistence.Database.Entity; using System.Data.Common; namespace Persistence.Database.Model; @@ -6,10 +8,12 @@ public partial class PersistenceDbContext : DbContext, IPersistenceDbContext { public DbSet DataSaub => Set(); + public DbSet TimestampedSets => Set(); + public PersistenceDbContext() : base() { - + } public PersistenceDbContext(DbContextOptions options) @@ -30,7 +34,9 @@ public partial class PersistenceDbContext : DbContext, IPersistenceDbContext { modelBuilder.HasPostgresExtension("adminpack") .HasAnnotation("Relational:Collation", "Russian_Russia.1251"); + + modelBuilder.Entity() + .Property(e => e.Set) + .HasJsonConversion(); } - - } diff --git a/Persistence.Database/EFExtensions.cs b/Persistence.Database/EFExtensions.cs new file mode 100644 index 0000000..c424aa3 --- /dev/null +++ b/Persistence.Database/EFExtensions.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Text.Json; +using System.Threading.Tasks; + +namespace Persistence.Database; + +public static class EFExtensions +{ + private static readonly JsonSerializerOptions jsonSerializerOptions = new() + { + AllowTrailingCommas = true, + WriteIndented = true, + NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals, + }; + + public static Microsoft.EntityFrameworkCore.Metadata.Builders.PropertyBuilder HasJsonConversion( + this Microsoft.EntityFrameworkCore.Metadata.Builders.PropertyBuilder builder) + => HasJsonConversion(builder, jsonSerializerOptions); + + public static Microsoft.EntityFrameworkCore.Metadata.Builders.PropertyBuilder HasJsonConversion( + this Microsoft.EntityFrameworkCore.Metadata.Builders.PropertyBuilder builder, + JsonSerializerOptions jsonSerializerOptions) + { + builder.HasConversion( + s => JsonSerializer.Serialize(s, jsonSerializerOptions), + s => JsonSerializer.Deserialize(s, jsonSerializerOptions)!); + + ValueComparer valueComparer = new( + (a, b) => + (a != null) && (b != null) + ? a.GetHashCode() == b.GetHashCode() + : (a == null) && (b == null), + i => (i == null) ? -1 : i.GetHashCode(), + i => i); + + builder.Metadata.SetValueComparer(valueComparer); + return builder; + } + +} + diff --git a/Persistence.Database/Entity/TimestampedSet.cs b/Persistence.Database/Entity/TimestampedSet.cs new file mode 100644 index 0000000..c9a0dda --- /dev/null +++ b/Persistence.Database/Entity/TimestampedSet.cs @@ -0,0 +1,11 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Persistence.Database.Entity; + +[Comment("Общая таблица данных временных рядов")] +[PrimaryKey(nameof(IdDiscriminator), nameof(Timestamp))] +public record TimestampedSet( + [property: Comment("Дискриминатор ссылка на тип сохраняемых данных")] Guid IdDiscriminator, + [property: Comment("Отметка времени, строго в UTC")] DateTimeOffset Timestamp, + [property: Column(TypeName = "jsonb"), Comment("Набор сохраняемых данных")] IDictionary Set); diff --git a/Persistence.Database/IPersistenceDbContext.cs b/Persistence.Database/IPersistenceDbContext.cs index 66f34ff..d23656e 100644 --- a/Persistence.Database/IPersistenceDbContext.cs +++ b/Persistence.Database/IPersistenceDbContext.cs @@ -1,8 +1,13 @@ using Microsoft.EntityFrameworkCore; +using Persistence.Database.Entity; using Persistence.Database.Model; +using System.Diagnostics.CodeAnalysis; namespace Persistence.Database; public interface IPersistenceDbContext : IDisposable { DbSet DataSaub { get; } + DbSet TimestampedSets { get; } + DbSet Set<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.Interfaces)] TEntity>() where TEntity : class; + Task SaveChangesAsync(CancellationToken cancellationToken); } diff --git a/Persistence.IntegrationTests/Clients/ITimestampedSetClient.cs b/Persistence.IntegrationTests/Clients/ITimestampedSetClient.cs new file mode 100644 index 0000000..96e2500 --- /dev/null +++ b/Persistence.IntegrationTests/Clients/ITimestampedSetClient.cs @@ -0,0 +1,23 @@ +using Persistence.Models; +using Refit; + +namespace Persistence.IntegrationTests.Clients; +public interface ITimestampedSetClient +{ + private const string baseUrl = "/api/TimestampedSet/{idDiscriminator}"; + + [Post(baseUrl)] + Task> InsertRange(Guid idDiscriminator, IEnumerable sets); + + [Get(baseUrl)] + Task>> Get(Guid idDiscriminator, [Query]DateTimeOffset? geTimestamp, [Query]IEnumerable? props, int skip, int take); + + [Get($"{baseUrl}/last")] + Task>> GetLast(Guid idDiscriminator, [Query] IEnumerable? props, int take); + + [Get($"{baseUrl}/count")] + Task> Count(Guid idDiscriminator); + + [Get($"{baseUrl}/datesRange")] + Task> GetDatesRange(Guid idDiscriminator); +} diff --git a/Persistence.IntegrationTests/Controllers/TimestampedSetControllerTest.cs b/Persistence.IntegrationTests/Controllers/TimestampedSetControllerTest.cs new file mode 100644 index 0000000..12ebb94 --- /dev/null +++ b/Persistence.IntegrationTests/Controllers/TimestampedSetControllerTest.cs @@ -0,0 +1,219 @@ +using Microsoft.AspNetCore.Mvc; +using Persistence.IntegrationTests.Clients; +using Persistence.Models; +using Xunit; + +namespace Persistence.IntegrationTests.Controllers; +public class TimestampedSetControllerTest : BaseIntegrationTest +{ + private readonly ITimestampedSetClient client; + + public TimestampedSetControllerTest(WebAppFactoryFixture factory) : base(factory) + { + + client = factory.GetAuthorizedHttpClient(string.Empty).Result; + } + + [Fact] + public async Task InsertRange() + { + // arrange + Guid idDiscriminator = Guid.NewGuid(); + IEnumerable testSets = Generate(10, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7))); + + // act + var response = await client.InsertRange(idDiscriminator, testSets); + + // assert + Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); + Assert.Equal(testSets.Count(), response.Content); + } + + [Fact] + public async Task Get_without_filter() + { + // arrange + Guid idDiscriminator = Guid.NewGuid(); + int count = 10; + IEnumerable testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7))); + var insertResponse = await client.InsertRange(idDiscriminator, testSets); + + // act + var response = await client.Get(idDiscriminator, null, null, 0, int.MaxValue); + + // assert + Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + var items = response.Content!; + Assert.Equal(count, items.Count()); + } + + [Fact] + public async Task Get_with_filter_props() + { + // arrange + Guid idDiscriminator = Guid.NewGuid(); + int count = 10; + IEnumerable testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7))); + var insertResponse = await client.InsertRange(idDiscriminator, testSets); + string[] props = ["A"]; + + // act + var response = await client.Get(idDiscriminator, null, props, 0, int.MaxValue); + + // assert + Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + var items = response.Content!; + Assert.Equal(count, items.Count()); + foreach ( var item in items ) + { + Assert.Single(item.Set); + var kv = item.Set.First(); + Assert.Equal("A", kv.Key); + } + } + + [Fact] + public async Task Get_geDate() + { + // arrange + Guid idDiscriminator = Guid.NewGuid(); + int count = 10; + var dateMin = DateTimeOffset.Now; + var dateMax = DateTimeOffset.Now.AddSeconds(count); + IEnumerable testSets = Generate(count, dateMin.ToOffset(TimeSpan.FromHours(7))); + var insertResponse = await client.InsertRange(idDiscriminator, testSets); + var tail = testSets.OrderBy(t => t.Timestamp).Skip(count / 2).Take(int.MaxValue); + var geDate = tail.First().Timestamp; + var tolerance = TimeSpan.FromSeconds(1); + var expectedCount = tail.Count(); + + // act + var response = await client.Get(idDiscriminator, geDate, null, 0, int.MaxValue); + + // assert + Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + var items = response.Content!; + Assert.Equal(expectedCount, items.Count()); + var minDate = items.Min(t => t.Timestamp); + Assert.Equal(geDate, geDate, tolerance); + } + + [Fact] + public async Task Get_with_skip_take() + { + // arrange + Guid idDiscriminator = Guid.NewGuid(); + int count = 10; + IEnumerable testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7))); + var insertResponse = await client.InsertRange(idDiscriminator, testSets); + var expectedCount = count / 2; + + // act + var response = await client.Get(idDiscriminator, null, null, 2, expectedCount); + + // assert + Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + var items = response.Content!; + Assert.Equal(expectedCount, items.Count()); + } + + + [Fact] + public async Task Get_with_big_skip_take() + { + // arrange + Guid idDiscriminator = Guid.NewGuid(); + var expectedCount = 1; + int count = 10 + expectedCount; + IEnumerable testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7))); + var insertResponse = await client.InsertRange(idDiscriminator, testSets); + + // act + var response = await client.Get(idDiscriminator, null, null, count - expectedCount, count); + + // assert + Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + var items = response.Content!; + Assert.Equal(expectedCount, items.Count()); + } + + [Fact] + public async Task GetLast() + { + // arrange + Guid idDiscriminator = Guid.NewGuid(); + int count = 10; + IEnumerable testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7))); + var insertResponse = await client.InsertRange(idDiscriminator, testSets); + var expectedCount = 8; + + // act + var response = await client.GetLast(idDiscriminator, null, expectedCount); + + // assert + Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + var items = response.Content!; + Assert.Equal(expectedCount, items.Count()); + } + + [Fact] + public async Task GetDatesRange() + { + // arrange + Guid idDiscriminator = Guid.NewGuid(); + int count = 10; + var dateMin = DateTimeOffset.Now; + var dateMax = DateTimeOffset.Now.AddSeconds(count-1); + IEnumerable testSets = Generate(count, dateMin.ToOffset(TimeSpan.FromHours(7))); + var insertResponse = await client.InsertRange(idDiscriminator, testSets); + var tolerance = TimeSpan.FromSeconds(1); + + // act + var response = await client.GetDatesRange(idDiscriminator); + + // assert + Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + var range = response.Content!; + Assert.Equal(dateMin, range.From, tolerance); + Assert.Equal(dateMax, range.To, tolerance); + } + + [Fact] + public async Task Count() + { + // arrange + Guid idDiscriminator = Guid.NewGuid(); + int count = 144; + IEnumerable testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7))); + var insertResponse = await client.InsertRange(idDiscriminator, testSets); + + // act + var response = await client.Count(idDiscriminator); + + // assert + Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); + Assert.Equal(count, response.Content); + } + + private static IEnumerable Generate(int n, DateTimeOffset from) + { + for (int i = 0; i < n; i++) + yield return new TimestampedSetDto + ( + from.AddSeconds(i), + new Dictionary{ + {"A", i }, + {"B", i * 1.1 }, + {"C", $"Any{i}" }, + {"D", DateTimeOffset.Now}, + } + ); + } +} diff --git a/Persistence.IntegrationTests/WebAppFactoryFixture.cs b/Persistence.IntegrationTests/WebAppFactoryFixture.cs index 8ac7bf2..740d3c5 100644 --- a/Persistence.IntegrationTests/WebAppFactoryFixture.cs +++ b/Persistence.IntegrationTests/WebAppFactoryFixture.cs @@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Persistence.API; +using Persistence.Database; using Persistence.Database.Model; using Persistence.Database.Postgres; using Refit; @@ -53,6 +54,8 @@ public class WebAppFactoryFixture : WebApplicationFactory services.AddDbContext(options => options.UseNpgsql(connectionString)); + services.AddScoped(provider => provider.GetRequiredService()); + var serviceProvider = services.BuildServiceProvider(); using var scope = serviceProvider.CreateScope(); diff --git a/Persistence.Repository/DependencyInjection.cs b/Persistence.Repository/DependencyInjection.cs index 8e2f759..3a27ed1 100644 --- a/Persistence.Repository/DependencyInjection.cs +++ b/Persistence.Repository/DependencyInjection.cs @@ -16,6 +16,7 @@ public static class DependencyInjection MapsterSetup(); services.AddTransient, TimeSeriesDataCachedRepository>(); + services.AddTransient(); return services; } diff --git a/Persistence.Repository/Repositories/TimestampedSetRepository.cs b/Persistence.Repository/Repositories/TimestampedSetRepository.cs new file mode 100644 index 0000000..a81f7f1 --- /dev/null +++ b/Persistence.Repository/Repositories/TimestampedSetRepository.cs @@ -0,0 +1,110 @@ +using Microsoft.EntityFrameworkCore; +using Persistence.Database; +using Persistence.Database.Entity; +using Persistence.Models; +using Persistence.Repositories; + +namespace Persistence.Repository.Repositories; +public class TimestampedSetRepository : ITimestampedSetRepository +{ + private readonly IPersistenceDbContext db; + + public TimestampedSetRepository(IPersistenceDbContext db) + { + this.db = db; + } + + public Task InsertRange(Guid idDiscriminator, IEnumerable sets, CancellationToken token) + { + var entities = sets.Select(set => new TimestampedSet(idDiscriminator, set.Timestamp.ToUniversalTime(), set.Set)); + var dbSet = db.Set(); + dbSet.AddRange(entities); + return db.SaveChangesAsync(token); + } + + public async Task> Get(Guid idDiscriminator, DateTimeOffset? geTimestamp, IEnumerable? props, int skip, int take, CancellationToken token) + { + var dbSet = db.Set(); + var query = dbSet.Where(entity => entity.IdDiscriminator == idDiscriminator); + + if (geTimestamp.HasValue) + query = ApplyGeTimestamp(query, geTimestamp.Value); + + var data = await Materialize(query, token); + + if (props is not null && props.Any()) + data = ApplyPropsFilter(data, props); + + return data; + } + + public async Task> GetLast(Guid idDiscriminator, IEnumerable? props, int take, CancellationToken token) + { + var dbSet = db.Set(); + 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 (props is not null && props.Any()) + data = ApplyPropsFilter(data, props); + + return data; + } + + public Task Count(Guid idDiscriminator, CancellationToken token) + { + var dbSet = db.Set(); + var query = dbSet.Where(entity => entity.IdDiscriminator == idDiscriminator); + return query.CountAsync(token); + } + + public async Task GetDatesRange(Guid idDiscriminator, CancellationToken token) + { + var query = db.Set() + .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> Materialize(IQueryable 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 ApplyGeTimestamp(IQueryable query, DateTimeOffset geTimestamp) + { + var geTimestampUtc = geTimestamp.ToUniversalTime(); + return query.Where(entity => entity.Timestamp >= geTimestampUtc); + } + + private static IEnumerable ApplyPropsFilter(IEnumerable query, IEnumerable props) + { + var newQuery = query + .Select(entity => new TimestampedSetDto( + entity.Timestamp, + entity.Set + .Where(prop => props.Contains(prop.Key)) + .ToDictionary(prop => prop.Key, prop => prop.Value) + )); + return newQuery; + } +} diff --git a/Persistence/Models/TimestampedSetDto.cs b/Persistence/Models/TimestampedSetDto.cs new file mode 100644 index 0000000..3235a4e --- /dev/null +++ b/Persistence/Models/TimestampedSetDto.cs @@ -0,0 +1,8 @@ +namespace Persistence.Models; + +/// +/// набор данных с отметкой времени +/// +/// отметка времени +/// набор данных +public record TimestampedSetDto(DateTimeOffset Timestamp, IDictionary Set); diff --git a/Persistence/Repositories/ITimestampedSetRepository.cs b/Persistence/Repositories/ITimestampedSetRepository.cs new file mode 100644 index 0000000..3e853f1 --- /dev/null +++ b/Persistence/Repositories/ITimestampedSetRepository.cs @@ -0,0 +1,11 @@ +using Persistence.Models; + +namespace Persistence.Repositories; +public interface ITimestampedSetRepository +{ + Task Count(Guid idDiscriminator, CancellationToken token); + Task> Get(Guid idDiscriminator, DateTimeOffset? geTimestamp, IEnumerable? props, int skip, int take, CancellationToken token); + Task GetDatesRange(Guid idDiscriminator, CancellationToken token); + Task> GetLast(Guid idDiscriminator, IEnumerable? props, int take, CancellationToken token); + Task InsertRange(Guid idDiscriminator, IEnumerable sets, CancellationToken token); +} \ No newline at end of file From bd8de9afc2598f5068e09f6a306d41d40d05a86f Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Mon, 25 Nov 2024 09:41:11 +0500 Subject: [PATCH 2/5] Add TimestampedSet documentation --- Persistence.Database/EFExtensions.cs | 7 --- .../Repositories/TimestampedSetRepository.cs | 7 +++ .../Repositories/ITimestampedSetRepository.cs | 48 +++++++++++++++++++ 3 files changed, 55 insertions(+), 7 deletions(-) diff --git a/Persistence.Database/EFExtensions.cs b/Persistence.Database/EFExtensions.cs index c424aa3..d60d768 100644 --- a/Persistence.Database/EFExtensions.cs +++ b/Persistence.Database/EFExtensions.cs @@ -1,13 +1,6 @@ using Microsoft.EntityFrameworkCore.ChangeTracking; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Text.Json.Serialization; using System.Text.Json; -using System.Threading.Tasks; namespace Persistence.Database; diff --git a/Persistence.Repository/Repositories/TimestampedSetRepository.cs b/Persistence.Repository/Repositories/TimestampedSetRepository.cs index a81f7f1..a190d71 100644 --- a/Persistence.Repository/Repositories/TimestampedSetRepository.cs +++ b/Persistence.Repository/Repositories/TimestampedSetRepository.cs @@ -5,6 +5,13 @@ using Persistence.Models; using Persistence.Repositories; namespace Persistence.Repository.Repositories; + +/// +/// Репозиторий для хранения разных наборов данных временных рядов. +/// idDiscriminator - идентифицирует конкретный набор данных, прим.: циклы измерения АСИБР, или отчет о DrillTest. +/// idDiscriminator формируют клиенты и только им известно что они обозначают. +/// Так как данные приходят редко, то их прореживания для построения графиков не предусмотрено. +/// public class TimestampedSetRepository : ITimestampedSetRepository { private readonly IPersistenceDbContext db; diff --git a/Persistence/Repositories/ITimestampedSetRepository.cs b/Persistence/Repositories/ITimestampedSetRepository.cs index 3e853f1..2966a82 100644 --- a/Persistence/Repositories/ITimestampedSetRepository.cs +++ b/Persistence/Repositories/ITimestampedSetRepository.cs @@ -1,11 +1,59 @@ using Persistence.Models; namespace Persistence.Repositories; + +/// +/// Репозиторий для хранения разных наборов данных временных рядов. +/// idDiscriminator - идентифицирует конкретный набор данных, прим.: циклы измерения АСИБР, или отчет о DrillTest. +/// idDiscriminator формируют клиенты и только им известно что они обозначают. +/// Так как данные приходят редко, то их прореживания для построения графиков не предусмотрено. +/// public interface ITimestampedSetRepository { + /// + /// Количество записей по указанному набору в БД. Для пагинации. + /// + /// + /// + /// Task Count(Guid idDiscriminator, CancellationToken token); + + /// + /// Получение данных с фильтрацией. Значение фильтра null - отключен + /// + /// Идентификатор набора + /// Фильтр позднее даты + /// Фильтр свойств набора. Можно запросить только некоторые свойства из набора + /// + /// + /// + /// Task> Get(Guid idDiscriminator, DateTimeOffset? geTimestamp, IEnumerable? props, int skip, int take, CancellationToken token); + + /// + /// Диапазон дат за которые есть данные + /// + /// + /// + /// Task GetDatesRange(Guid idDiscriminator, CancellationToken token); + + /// + /// Получить последние данные + /// + /// + /// + /// + /// + /// Task> GetLast(Guid idDiscriminator, IEnumerable? props, int take, CancellationToken token); + + /// + /// Добавление новых данных + /// + /// + /// + /// + /// Task InsertRange(Guid idDiscriminator, IEnumerable sets, CancellationToken token); } \ No newline at end of file From 2169e592e6dc5b59492fcf4e9549e341aab4223e Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Tue, 26 Nov 2024 10:07:50 +0500 Subject: [PATCH 3/5] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20ITimestampedSetClient.=20=D0=9F=D0=BE=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D1=82=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D1=8B.=20=D0=A3=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=20=D0=BD=D0=B5?= =?UTF-8?q?=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D1=83=D0=B5=D0=BC?= =?UTF-8?q?=D1=8B=D0=B9=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D1=84=D0=B5=D0=B9?= =?UTF-8?q?=D1=81=20IPersistenceDbContext.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Clients/ITimestampedSetClient.cs | 4 ++-- .../PersistenceDbContext.cs | 2 +- Persistence.Database/IPersistenceDbContext.cs | 13 ------------- .../Model/IPersistenceDbContext.cs | 17 ----------------- .../Controllers/TimestampedSetControllerTest.cs | 9 ++++++--- .../WebAppFactoryFixture.cs | 3 --- .../Repositories/TimestampedSetRepository.cs | 10 +++++++--- .../Repositories/ITimestampedSetRepository.cs | 8 ++++---- 8 files changed, 20 insertions(+), 46 deletions(-) delete mode 100644 Persistence.Database/IPersistenceDbContext.cs delete mode 100644 Persistence.Database/Model/IPersistenceDbContext.cs diff --git a/Persistence.Client/Clients/ITimestampedSetClient.cs b/Persistence.Client/Clients/ITimestampedSetClient.cs index 96e2500..37b62be 100644 --- a/Persistence.Client/Clients/ITimestampedSetClient.cs +++ b/Persistence.Client/Clients/ITimestampedSetClient.cs @@ -1,7 +1,7 @@ using Persistence.Models; using Refit; -namespace Persistence.IntegrationTests.Clients; +namespace Persistence.Client.Clients; public interface ITimestampedSetClient { private const string baseUrl = "/api/TimestampedSet/{idDiscriminator}"; @@ -10,7 +10,7 @@ public interface ITimestampedSetClient Task> InsertRange(Guid idDiscriminator, IEnumerable sets); [Get(baseUrl)] - Task>> Get(Guid idDiscriminator, [Query]DateTimeOffset? geTimestamp, [Query]IEnumerable? props, int skip, int take); + Task>> Get(Guid idDiscriminator, [Query] DateTimeOffset? geTimestamp, [Query] IEnumerable? props, int skip, int take); [Get($"{baseUrl}/last")] Task>> GetLast(Guid idDiscriminator, [Query] IEnumerable? props, int take); diff --git a/Persistence.Database.Postgres/PersistenceDbContext.cs b/Persistence.Database.Postgres/PersistenceDbContext.cs index 053df12..a0cae6a 100644 --- a/Persistence.Database.Postgres/PersistenceDbContext.cs +++ b/Persistence.Database.Postgres/PersistenceDbContext.cs @@ -4,7 +4,7 @@ using Persistence.Database.Entity; using System.Data.Common; namespace Persistence.Database.Model; -public partial class PersistenceDbContext : DbContext, IPersistenceDbContext +public partial class PersistenceDbContext : DbContext { public DbSet DataSaub => Set(); diff --git a/Persistence.Database/IPersistenceDbContext.cs b/Persistence.Database/IPersistenceDbContext.cs deleted file mode 100644 index d23656e..0000000 --- a/Persistence.Database/IPersistenceDbContext.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Persistence.Database.Entity; -using Persistence.Database.Model; -using System.Diagnostics.CodeAnalysis; - -namespace Persistence.Database; -public interface IPersistenceDbContext : IDisposable -{ - DbSet DataSaub { get; } - DbSet TimestampedSets { get; } - DbSet Set<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties | DynamicallyAccessedMemberTypes.Interfaces)] TEntity>() where TEntity : class; - Task SaveChangesAsync(CancellationToken cancellationToken); -} diff --git a/Persistence.Database/Model/IPersistenceDbContext.cs b/Persistence.Database/Model/IPersistenceDbContext.cs deleted file mode 100644 index 2c1aebb..0000000 --- a/Persistence.Database/Model/IPersistenceDbContext.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using System; -using System.Collections.Generic; -using System.Diagnostics.Metrics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Persistence.Database.Model; -public interface IPersistenceDbContext : IDisposable -{ - DbSet DataSaub { get; } - DbSet Setpoint { get; } - DatabaseFacade Database { get; } - Task SaveChangesAsync(CancellationToken cancellationToken); -} diff --git a/Persistence.IntegrationTests/Controllers/TimestampedSetControllerTest.cs b/Persistence.IntegrationTests/Controllers/TimestampedSetControllerTest.cs index 12ebb94..aa33e1b 100644 --- a/Persistence.IntegrationTests/Controllers/TimestampedSetControllerTest.cs +++ b/Persistence.IntegrationTests/Controllers/TimestampedSetControllerTest.cs @@ -1,5 +1,6 @@ -using Microsoft.AspNetCore.Mvc; -using Persistence.IntegrationTests.Clients; +using Microsoft.Extensions.DependencyInjection; +using Persistence.Client; +using Persistence.Client.Clients; using Persistence.Models; using Xunit; @@ -10,8 +11,10 @@ public class TimestampedSetControllerTest : BaseIntegrationTest public TimestampedSetControllerTest(WebAppFactoryFixture factory) : base(factory) { + var persistenceClientFactory = scope.ServiceProvider + .GetRequiredService(); - client = factory.GetAuthorizedHttpClient(string.Empty).Result; + client = persistenceClientFactory.GetClient(); } [Fact] diff --git a/Persistence.IntegrationTests/WebAppFactoryFixture.cs b/Persistence.IntegrationTests/WebAppFactoryFixture.cs index 1d99a2a..7b12362 100644 --- a/Persistence.IntegrationTests/WebAppFactoryFixture.cs +++ b/Persistence.IntegrationTests/WebAppFactoryFixture.cs @@ -43,9 +43,6 @@ public class WebAppFactoryFixture : WebApplicationFactory services.AddSingleton(); var serviceProvider = services.BuildServiceProvider(); - services.AddScoped(provider => provider.GetRequiredService()); - - var serviceProvider = services.BuildServiceProvider(); using var scope = serviceProvider.CreateScope(); var scopedServices = scope.ServiceProvider; diff --git a/Persistence.Repository/Repositories/TimestampedSetRepository.cs b/Persistence.Repository/Repositories/TimestampedSetRepository.cs index a190d71..ab6e1a8 100644 --- a/Persistence.Repository/Repositories/TimestampedSetRepository.cs +++ b/Persistence.Repository/Repositories/TimestampedSetRepository.cs @@ -1,5 +1,4 @@ using Microsoft.EntityFrameworkCore; -using Persistence.Database; using Persistence.Database.Entity; using Persistence.Models; using Persistence.Repositories; @@ -14,9 +13,9 @@ namespace Persistence.Repository.Repositories; /// public class TimestampedSetRepository : ITimestampedSetRepository { - private readonly IPersistenceDbContext db; + private readonly DbContext db; - public TimestampedSetRepository(IPersistenceDbContext db) + public TimestampedSetRepository(DbContext db) { this.db = db; } @@ -37,6 +36,11 @@ public class TimestampedSetRepository : ITimestampedSetRepository 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 (props is not null && props.Any()) diff --git a/Persistence/Repositories/ITimestampedSetRepository.cs b/Persistence/Repositories/ITimestampedSetRepository.cs index 2966a82..980e47c 100644 --- a/Persistence/Repositories/ITimestampedSetRepository.cs +++ b/Persistence/Repositories/ITimestampedSetRepository.cs @@ -21,7 +21,7 @@ public interface ITimestampedSetRepository /// /// Получение данных с фильтрацией. Значение фильтра null - отключен /// - /// Идентификатор набора + /// Дискриминатор (идентификатор) набора /// Фильтр позднее даты /// Фильтр свойств набора. Можно запросить только некоторые свойства из набора /// @@ -41,8 +41,8 @@ public interface ITimestampedSetRepository /// /// Получить последние данные /// - /// - /// + /// Дискриминатор (идентификатор) набора + /// Фильтр свойств набора. Можно запросить только некоторые свойства из набора /// /// /// @@ -51,7 +51,7 @@ public interface ITimestampedSetRepository /// /// Добавление новых данных /// - /// + /// Дискриминатор (идентификатор) набора /// /// /// From a07dbae5b87bdae3bc72211855d952b4bbcb70bd Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Tue, 26 Nov 2024 11:24:31 +0500 Subject: [PATCH 4/5] TimestampedSetRepository rename private ApplyPropsFilter to ReduceSetColumnsByNames; Fix TimestampedSetController doc and response type; ITimestampedSetClient Add doc. --- Persistence.Repository/Repositories/TimestampedSetRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Persistence.Repository/Repositories/TimestampedSetRepository.cs b/Persistence.Repository/Repositories/TimestampedSetRepository.cs index ab6e1a8..0ba4e5b 100644 --- a/Persistence.Repository/Repositories/TimestampedSetRepository.cs +++ b/Persistence.Repository/Repositories/TimestampedSetRepository.cs @@ -107,7 +107,7 @@ public class TimestampedSetRepository : ITimestampedSetRepository return query.Where(entity => entity.Timestamp >= geTimestampUtc); } - private static IEnumerable ApplyPropsFilter(IEnumerable query, IEnumerable props) + private static IEnumerable ReduceSetColumnsByNames(IEnumerable query, IEnumerable columnNames) { var newQuery = query .Select(entity => new TimestampedSetDto( From 3bb5fc4411b35db19edc583e67b583a6c0864974 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Tue, 26 Nov 2024 11:24:31 +0500 Subject: [PATCH 5/5] TimestampedSetRepository rename private ApplyPropsFilter to ReduceSetColumnsByNames; Fix TimestampedSetController doc and response type; ITimestampedSetClient Add doc. --- .../Controllers/TimestampedSetController.cs | 53 ++++++++++++++++--- .../Clients/ITimestampedSetClient.cs | 43 ++++++++++++++- .../Repositories/TimestampedSetRepository.cs | 16 +++--- .../Repositories/ITimestampedSetRepository.cs | 16 +++--- 4 files changed, 104 insertions(+), 24 deletions(-) diff --git a/Persistence.API/Controllers/TimestampedSetController.cs b/Persistence.API/Controllers/TimestampedSetController.cs index 3d7ce63..f18e4c8 100644 --- a/Persistence.API/Controllers/TimestampedSetController.cs +++ b/Persistence.API/Controllers/TimestampedSetController.cs @@ -2,11 +2,14 @@ using Microsoft.AspNetCore.Mvc; using Persistence.Models; using Persistence.Repositories; -using Persistence.Repository.Repositories; using System.Net; namespace Persistence.API.Controllers; +/// +/// Хранение наборов данных с отметкой времени. +/// Не оптимизировано под большие данные. +/// [ApiController] [Authorize] [Route("api/[controller]/{idDiscriminator}")] @@ -19,6 +22,14 @@ public class TimestampedSetController : ControllerBase this.repository = repository; } + /// + /// Записать новые данные + /// Предполагается что данные с одним дискриминатором имеют одинаковую структуру + /// + /// Дискриминатор (идентификатор) набора + /// + /// + /// кол-во затронутых записей [HttpPost] [ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)] public async Task InsertRange([FromRoute]Guid idDiscriminator, [FromBody]IEnumerable sets, CancellationToken token) @@ -27,22 +38,46 @@ public class TimestampedSetController : ControllerBase return Ok(result); } + /// + /// Получение данных с фильтрацией. Значение фильтра null - отключен + /// + /// Дискриминатор (идентификатор) набора + /// Фильтр позднее даты + /// Фильтр свойств набора. Можно запросить только некоторые свойства из набора + /// + /// + /// + /// Фильтрованный набор данных с сортировкой по отметке времени [HttpGet] [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)] - public async Task Get(Guid idDiscriminator, DateTimeOffset? geTimestamp, [FromQuery]IEnumerable? props, int skip, int take, CancellationToken token) + public async Task Get(Guid idDiscriminator, DateTimeOffset? geTimestamp, [FromQuery]IEnumerable? columnNames, int skip, int take, CancellationToken token) { - var result = await repository.Get(idDiscriminator, geTimestamp, props, skip, take, token); + var result = await repository.Get(idDiscriminator, geTimestamp, columnNames, skip, take, token); return Ok(result); } + /// + /// Получить последние данные + /// + /// Дискриминатор (идентификатор) набора + /// Фильтр свойств набора. Можно запросить только некоторые свойства из набора + /// + /// + /// Фильтрованный набор данных с сортировкой по отметке времени [HttpGet("last")] [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)] - public async Task GetLast(Guid idDiscriminator, [FromQuery]IEnumerable? props, int take, CancellationToken token) + public async Task GetLast(Guid idDiscriminator, [FromQuery]IEnumerable? columnNames, int take, CancellationToken token) { - var result = await repository.GetLast(idDiscriminator, props, take, token); + var result = await repository.GetLast(idDiscriminator, columnNames, take, token); return Ok(result); } + /// + /// Диапазон дат за которые есть данные + /// + /// + /// + /// Дата первой и последней записи [HttpGet("datesRange")] [ProducesResponseType(typeof(DatesRangeDto), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.NoContent)] @@ -52,8 +87,14 @@ public class TimestampedSetController : ControllerBase return Ok(result); } + /// + /// Количество записей по указанному набору в БД. Для пагинации. + /// + /// Дискриминатор (идентификатор) набора + /// + /// [HttpGet("count")] - [ProducesResponseType(typeof(DatesRangeDto), (int)HttpStatusCode.OK)] + [ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.NoContent)] public async Task Count(Guid idDiscriminator, CancellationToken token) { diff --git a/Persistence.Client/Clients/ITimestampedSetClient.cs b/Persistence.Client/Clients/ITimestampedSetClient.cs index 37b62be..95e8bd1 100644 --- a/Persistence.Client/Clients/ITimestampedSetClient.cs +++ b/Persistence.Client/Clients/ITimestampedSetClient.cs @@ -2,22 +2,61 @@ using Refit; namespace Persistence.Client.Clients; + +/// +/// Клиент для работы с репозиторием для хранения разных наборов данных рядов. +/// idDiscriminator - идентифицирует конкретный набор данных, прим.: циклы измерения АСИБР, или отчет о DrillTest. +/// idDiscriminator формируют клиенты и только им известно что они обозначают. +/// Так как данные приходят редко, то их прореживания для построения графиков не предусмотрено. +/// public interface ITimestampedSetClient { private const string baseUrl = "/api/TimestampedSet/{idDiscriminator}"; + /// + /// Добавление новых данных + /// + /// Дискриминатор (идентификатор) набора + /// + /// [Post(baseUrl)] Task> InsertRange(Guid idDiscriminator, IEnumerable sets); + /// + /// Получение данных с фильтрацией. Значение фильтра null - отключен + /// + /// Дискриминатор (идентификатор) набора + /// Фильтр позднее даты + /// Фильтр свойств набора. Можно запросить только некоторые свойства из набора + /// + /// + /// [Get(baseUrl)] - Task>> Get(Guid idDiscriminator, [Query] DateTimeOffset? geTimestamp, [Query] IEnumerable? props, int skip, int take); + Task>> Get(Guid idDiscriminator, [Query] DateTimeOffset? geTimestamp, [Query] IEnumerable? columnNames, int skip, int take); + /// + /// Получить последние данные + /// + /// Дискриминатор (идентификатор) набора + /// Фильтр свойств набора. Можно запросить только некоторые свойства из набора + /// + /// [Get($"{baseUrl}/last")] - Task>> GetLast(Guid idDiscriminator, [Query] IEnumerable? props, int take); + Task>> GetLast(Guid idDiscriminator, [Query] IEnumerable? columnNames, int take); + /// + /// Количество записей по указанному набору в БД. Для пагинации. + /// + /// Дискриминатор (идентификатор) набора + /// [Get($"{baseUrl}/count")] Task> Count(Guid idDiscriminator); + /// + /// Диапазон дат за которые есть данные + /// + /// Дискриминатор (идентификатор) набора + /// [Get($"{baseUrl}/datesRange")] Task> GetDatesRange(Guid idDiscriminator); } diff --git a/Persistence.Repository/Repositories/TimestampedSetRepository.cs b/Persistence.Repository/Repositories/TimestampedSetRepository.cs index ab6e1a8..ad9a6cf 100644 --- a/Persistence.Repository/Repositories/TimestampedSetRepository.cs +++ b/Persistence.Repository/Repositories/TimestampedSetRepository.cs @@ -28,7 +28,7 @@ public class TimestampedSetRepository : ITimestampedSetRepository return db.SaveChangesAsync(token); } - public async Task> Get(Guid idDiscriminator, DateTimeOffset? geTimestamp, IEnumerable? props, int skip, int take, CancellationToken token) + 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.IdDiscriminator == idDiscriminator); @@ -43,13 +43,13 @@ public class TimestampedSetRepository : ITimestampedSetRepository var data = await Materialize(query, token); - if (props is not null && props.Any()) - data = ApplyPropsFilter(data, props); + if (columnNames is not null && columnNames.Any()) + data = ReduceSetColumnsByNames(data, columnNames); return data; } - public async Task> GetLast(Guid idDiscriminator, IEnumerable? props, int take, CancellationToken token) + public async Task> GetLast(Guid idDiscriminator, IEnumerable? columnNames, int take, CancellationToken token) { var dbSet = db.Set(); var query = dbSet.Where(entity => entity.IdDiscriminator == idDiscriminator); @@ -60,8 +60,8 @@ public class TimestampedSetRepository : ITimestampedSetRepository var data = await Materialize(query, token); - if (props is not null && props.Any()) - data = ApplyPropsFilter(data, props); + if (columnNames is not null && columnNames.Any()) + data = ReduceSetColumnsByNames(data, columnNames); return data; } @@ -107,13 +107,13 @@ public class TimestampedSetRepository : ITimestampedSetRepository return query.Where(entity => entity.Timestamp >= geTimestampUtc); } - private static IEnumerable ApplyPropsFilter(IEnumerable query, IEnumerable props) + private static IEnumerable ReduceSetColumnsByNames(IEnumerable query, IEnumerable columnNames) { var newQuery = query .Select(entity => new TimestampedSetDto( entity.Timestamp, entity.Set - .Where(prop => props.Contains(prop.Key)) + .Where(prop => columnNames.Contains(prop.Key)) .ToDictionary(prop => prop.Key, prop => prop.Value) )); return newQuery; diff --git a/Persistence/Repositories/ITimestampedSetRepository.cs b/Persistence/Repositories/ITimestampedSetRepository.cs index 980e47c..27627c3 100644 --- a/Persistence/Repositories/ITimestampedSetRepository.cs +++ b/Persistence/Repositories/ITimestampedSetRepository.cs @@ -3,7 +3,7 @@ namespace Persistence.Repositories; /// -/// Репозиторий для хранения разных наборов данных временных рядов. +/// Репозиторий для хранения разных наборов данных рядов. /// idDiscriminator - идентифицирует конкретный набор данных, прим.: циклы измерения АСИБР, или отчет о DrillTest. /// idDiscriminator формируют клиенты и только им известно что они обозначают. /// Так как данные приходят редко, то их прореживания для построения графиков не предусмотрено. @@ -13,7 +13,7 @@ public interface ITimestampedSetRepository /// /// Количество записей по указанному набору в БД. Для пагинации. /// - /// + /// Дискриминатор (идентификатор) набора /// /// Task Count(Guid idDiscriminator, CancellationToken token); @@ -23,17 +23,17 @@ public interface ITimestampedSetRepository /// /// Дискриминатор (идентификатор) набора /// Фильтр позднее даты - /// Фильтр свойств набора. Можно запросить только некоторые свойства из набора + /// Фильтр свойств набора. Можно запросить только некоторые свойства из набора /// /// /// /// - Task> Get(Guid idDiscriminator, DateTimeOffset? geTimestamp, IEnumerable? props, int skip, int take, CancellationToken token); - + Task> Get(Guid idDiscriminator, DateTimeOffset? geTimestamp, IEnumerable? columnNames, int skip, int take, CancellationToken token); + /// /// Диапазон дат за которые есть данные /// - /// + /// Дискриминатор (идентификатор) набора /// /// Task GetDatesRange(Guid idDiscriminator, CancellationToken token); @@ -42,11 +42,11 @@ public interface ITimestampedSetRepository /// Получить последние данные /// /// Дискриминатор (идентификатор) набора - /// Фильтр свойств набора. Можно запросить только некоторые свойства из набора + /// Фильтр свойств набора. Можно запросить только некоторые свойства из набора /// /// /// - Task> GetLast(Guid idDiscriminator, IEnumerable? props, int take, CancellationToken token); + Task> GetLast(Guid idDiscriminator, IEnumerable? columnNames, int take, CancellationToken token); /// /// Добавление новых данных