Merge pull request '#974 Модифицировать метод Get для TimestampedValues по части применения фильтра' (#28) from TimestampedValuesFilter into master
Some checks failed
Unit tests / tests-and-publication (push) Failing after 1m22s

Reviewed-on: #28
Reviewed-by: on.nemtina <on.nemtina@digitaldrilling.ru>
This commit is contained in:
on.nemtina 2025-02-12 15:16:09 +05:00
commit a49a0f567a
38 changed files with 261 additions and 194 deletions

View File

@ -1,4 +1,5 @@
using DD.Persistence.Models;
using DD.Persistence.Filter.Models.Abstractions;
using DD.Persistence.Models;
using DD.Persistence.Models.Common;
using DD.Persistence.Repositories;
using DD.Persistence.Services.Interfaces;
@ -45,6 +46,7 @@ public class TimestampedValuesController : ControllerBase
/// </summary>
/// <param name="discriminatorIds">Набор дискриминаторов</param>
/// <param name="timestampBegin">Фильтр позднее даты</param>
/// <param name="filterTree">Кастомный фильтр по набору значений</param>
/// <param name="columnNames">Фильтр свойств набора</param>
/// <param name="skip"></param>
/// <param name="take"></param>
@ -52,9 +54,14 @@ public class TimestampedValuesController : ControllerBase
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<TimestampedValuesDto>), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
public async Task<ActionResult<IEnumerable<TimestampedValuesDto>>> Get([FromQuery] IEnumerable<Guid> discriminatorIds, DateTimeOffset? timestampBegin, [FromQuery] string[]? columnNames, int skip, int take, CancellationToken token)
public async Task<ActionResult<IEnumerable<TimestampedValuesDto>>> Get([FromQuery] IEnumerable<Guid> discriminatorIds,
DateTimeOffset? timestampBegin,
[FromQuery] TNode? filterTree,
[FromQuery] string[]? columnNames,
int skip, int take,
CancellationToken token)
{
var result = await timestampedValuesService.Get(discriminatorIds, timestampBegin, columnNames, skip, take, token);
var result = await timestampedValuesService.Get(discriminatorIds, timestampBegin, filterTree, columnNames, skip, take, token);
return result.Any() ? Ok(result) : NoContent();
}

View File

@ -1,16 +1,14 @@
using Mapster;
using DD.Persistence.Filter.Models.Abstractions;
using DD.Persistence.Models.Configurations;
using DD.Persistence.Services;
using DD.Persistence.Services.Interfaces;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using DD.Persistence.Models;
using DD.Persistence.Models.Configurations;
using DD.Persistence.Services;
using DD.Persistence.Services.Interfaces;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.Reflection;
using System.Text.Json.Nodes;
using DD.Persistence.Database.Entity;
namespace DD.Persistence.API;
@ -30,6 +28,7 @@ public static class DependencyInjection
new OpenApiSchema {Type = "number", Format = "float" }
]
});
c.MapType<TNode>(() => new OpenApiSchema { Type = "string" });
c.CustomOperationIds(e =>
{

View File

@ -24,12 +24,14 @@ public interface ITimestampedValuesClient : IDisposable
/// </summary>
/// <param name="discriminatorIds">Набор дискриминаторов (идентификаторов)</param>
/// <param name="timestampBegin">Фильтр позднее даты</param>
/// <param name="filterTree">Кастомный фильтр по набору значений</param>
/// <param name="columnNames">Фильтр свойств набора</param>
/// <param name="skip"></param>
/// <param name="take"></param>
/// <param name="token"></param>
Task<IEnumerable<TimestampedValuesDto>> Get(IEnumerable<Guid> discriminatorIds,
DateTimeOffset? timestampBegin,
string? filterTree,
IEnumerable<string>? columnNames,
int skip,
int take,
@ -40,11 +42,12 @@ public interface ITimestampedValuesClient : IDisposable
/// </summary>
/// <param name="discriminatorId"></param>
/// <param name="geTimestamp"></param>
/// <param name="filterTree"></param>
/// <param name="columnNames">Фильтр свойств набора</param>
/// <param name="skip"></param>
/// <param name="take"></param>
/// <param name="token"></param>
Task<IEnumerable<T>> Get<T>(Guid discriminatorId, DateTimeOffset? geTimestamp, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token);
Task<IEnumerable<T>> Get<T>(Guid discriminatorId, DateTimeOffset? geTimestamp, string? filterTree, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token);
/// <summary>
/// Получить данные, начиная с заданной отметки времени

View File

@ -23,6 +23,7 @@ public interface IRefitTimestampedValuesClient : IRefitClient, IDisposable
[Get($"{baseUrl}")]
Task<IApiResponse<IEnumerable<TimestampedValuesDto>>> Get([Query(CollectionFormat.Multi)] IEnumerable<Guid> discriminatorIds,
DateTimeOffset? timestampBegin,
[Query] string? filterTree,
[Query(CollectionFormat.Multi)] IEnumerable<string>? columnNames,
int skip,
int take,

View File

@ -32,17 +32,17 @@ public class TimestampedValuesClient : BaseClient, ITimestampedValuesClient
}
/// <inheritdoc/>
public async Task<IEnumerable<TimestampedValuesDto>> Get(IEnumerable<Guid> discriminatorIds, DateTimeOffset? geTimestamp, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token)
public async Task<IEnumerable<TimestampedValuesDto>> Get(IEnumerable<Guid> discriminatorIds, DateTimeOffset? geTimestamp, string? filterTree, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token)
{
var result = await ExecuteGetResponse(
async () => await refitTimestampedSetClient.Get(discriminatorIds, geTimestamp, columnNames, skip, take, token), token);
async () => await refitTimestampedSetClient.Get(discriminatorIds, geTimestamp, filterTree, columnNames, skip, take, token), token);
return result!;
}
/// <inheritdoc/>
public async Task<IEnumerable<T>> Get<T>(Guid discriminatorId, DateTimeOffset? geTimestamp, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token)
public async Task<IEnumerable<T>> Get<T>(Guid discriminatorId, DateTimeOffset? geTimestamp, string? filterTree, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token)
{
var data = await Get([discriminatorId], geTimestamp, columnNames, skip, take, token);
var data = await Get([discriminatorId], geTimestamp, filterTree, columnNames, skip, take, token);
var mapper = GetMapper<T>(discriminatorId);
return data.Select(mapper.DeserializeTimeStampedData);

View File

@ -13,7 +13,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace DD.Persistence.Database.Postgres.Migrations
{
[DbContext(typeof(PersistencePostgresContext))]
[Migration("20250205114037_Init")]
[Migration("20250210055116_Init")]
partial class Init
{
/// <inheritdoc />
@ -37,14 +37,14 @@ namespace DD.Persistence.Database.Postgres.Migrations
.HasColumnType("timestamp with time zone")
.HasComment("Дата создания записи");
b.Property<Guid>("DiscriminatorId")
.HasColumnType("uuid")
.HasComment("Дискриминатор таблицы");
b.Property<Guid>("IdAuthor")
.HasColumnType("uuid")
.HasComment("Автор изменения");
b.Property<Guid>("IdDiscriminator")
.HasColumnType("uuid")
.HasComment("Дискриминатор таблицы");
b.Property<Guid?>("IdEditor")
.HasColumnType("uuid")
.HasComment("Редактор");

View File

@ -17,7 +17,7 @@ namespace DD.Persistence.Database.Postgres.Migrations
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false, comment: "Ключ записи"),
IdDiscriminator = table.Column<Guid>(type: "uuid", nullable: false, comment: "Дискриминатор таблицы"),
DiscriminatorId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Дискриминатор таблицы"),
IdAuthor = table.Column<Guid>(type: "uuid", nullable: false, comment: "Автор изменения"),
IdEditor = table.Column<Guid>(type: "uuid", nullable: true, comment: "Редактор"),
Creation = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Дата создания записи"),

View File

@ -34,14 +34,14 @@ namespace DD.Persistence.Database.Postgres.Migrations
.HasColumnType("timestamp with time zone")
.HasComment("Дата создания записи");
b.Property<Guid>("DiscriminatorId")
.HasColumnType("uuid")
.HasComment("Дискриминатор таблицы");
b.Property<Guid>("IdAuthor")
.HasColumnType("uuid")
.HasComment("Автор изменения");
b.Property<Guid>("IdDiscriminator")
.HasColumnType("uuid")
.HasComment("Дискриминатор таблицы");
b.Property<Guid?>("IdEditor")
.HasColumnType("uuid")
.HasComment("Редактор");

View File

@ -42,6 +42,8 @@ public static class DependencyInjection
MapsterSetup();
//services.AddTransient(typeof(PersistenceRepository<TimestampedValues>));
services.AddTransient<ISetpointRepository, SetpointRepository>();
services.AddTransient<IChangeLogRepository, ChangeLogRepository>();
services.AddTransient<ITimestampedValuesRepository, TimestampedValuesRepository>();

View File

@ -11,13 +11,13 @@ namespace DD.Persistence.Database.Entity;
/// Часть записи, описывающая изменение
/// </summary>
[Table("change_log")]
public class ChangeLog : IChangeLog
public class ChangeLog : IDiscriminatorItem, IChangeLog
{
[Key, Comment("Ключ записи")]
public Guid Id { get; set; }
[Comment("Дискриминатор таблицы")]
public Guid IdDiscriminator { get; set; }
public Guid DiscriminatorId { get; set; }
[Comment("Автор изменения")]
public Guid IdAuthor { get; set; }

View File

@ -7,7 +7,7 @@ namespace DD.Persistence.Database.Entity;
[Table("parameter_data")]
[PrimaryKey(nameof(DiscriminatorId), nameof(ParameterId), nameof(Timestamp))]
public class ParameterData : ITimestampedItem
public class ParameterData : IDiscriminatorItem, ITimestampedItem
{
[Required, Comment("Дискриминатор системы")]
public Guid DiscriminatorId { get; set; }

View File

@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;
using DD.Persistence.Database.EntityAbstractions;
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json;
@ -7,7 +8,7 @@ namespace DD.Persistence.Database.Entity;
[Table("scheme_property")]
[PrimaryKey(nameof(DiscriminatorId), nameof(Index))]
public class SchemeProperty
public class SchemeProperty : IDiscriminatorItem
{
[Comment("Идентификатор схемы данных")]
public Guid DiscriminatorId { get; set; }

View File

@ -17,15 +17,15 @@ public class TechMessage : ITimestampedItem
[Comment("Дата возникновения")]
public DateTimeOffset Timestamp { get; set; }
[Column(TypeName = "varchar(512)"), Comment("Текст сообщения")]
public required string Text { get; set; }
[Column(TypeName = "varchar(512)"), Comment("Текст сообщения")]
public required string Text { get; set; }
[Required, Comment("Id системы, к которой относится сообщение")]
public required Guid SystemId { get; set; }
[Required, Comment("Id системы, к которой относится сообщение")]
public required Guid SystemId { get; set; }
[Required, ForeignKey(nameof(SystemId)), Comment("Система, к которой относится сообщение")]
public virtual required DataSourceSystem System { get; set; }
[Required, ForeignKey(nameof(SystemId)), Comment("Система, к которой относится сообщение")]
public virtual required DataSourceSystem System { get; set; }
[Comment("Статус события")]
public int EventState { get; set; }
}
[Comment("Статус события")]
public int EventState { get; set; }
}

View File

@ -7,7 +7,7 @@ namespace DD.Persistence.Database.Entity;
[Table("timestamped_values")]
[PrimaryKey(nameof(DiscriminatorId), nameof(Timestamp))]
public class TimestampedValues : ITimestampedItem, IValuesItem
public class TimestampedValues : IDiscriminatorItem, ITimestampedItem, IValuesItem
{
[Comment("Временная отметка"), Key]
public DateTimeOffset Timestamp { get; set; }

View File

@ -38,7 +38,7 @@ public interface IChangeLog
/// <summary>
/// Дискриминатор таблицы
/// </summary>
public Guid IdDiscriminator { get; set; }
public Guid DiscriminatorId { get; set; }
/// <summary>
/// Значение

View File

@ -0,0 +1,8 @@
namespace DD.Persistence.Database.EntityAbstractions;
public interface IDiscriminatorItem
{
/// <summary>
/// Дискриминатор
/// </summary>
Guid DiscriminatorId { get; set; }
}

View File

@ -1,7 +1,8 @@
using Ardalis.Specification;
using Ardalis.Specification.EntityFrameworkCore;
using DD.Persistence.Database.Entity;
using DD.Persistence.Database.EntityAbstractions;
using DD.Persistence.Database.Postgres.Extensions;
using DD.Persistence.Database.Specifications;
using DD.Persistence.Database.Specifications.Operation;
using DD.Persistence.Database.Specifications.ValuesItem;
using DD.Persistence.Filter.Models;
using DD.Persistence.Filter.Models.Abstractions;
@ -13,8 +14,17 @@ using System.Text.Json;
namespace DD.Persistence.Database.Postgres.Helpers;
public static class FilterBuilder
{
public static ISpecification<TEntity>? BuildFilter<TEntity>(this DataSchemeDto dataSchemeDto, TNode root)
where TEntity : IValuesItem
public static IQueryable<TEntity> ApplyFilter<TEntity>(this IQueryable<TEntity> query, DataSchemeDto dataSchemeDto, TNode root)
where TEntity : class, IValuesItem
{
var filterSpec = dataSchemeDto.BuildFilter<TEntity>(root);
if (filterSpec != null)
return query.WithSpecification(filterSpec);
return query;
}
private static ISpecification<TEntity>? BuildFilter<TEntity>(this DataSchemeDto dataSchemeDto, TNode root)
where TEntity : IValuesItem
{
var result = dataSchemeDto.BuildSpecificationByNextNode<TEntity>(root);
@ -48,10 +58,10 @@ public static class FilterBuilder
switch (vertex.Operation)
{
case OperationEnum.And:
result = new AndSpecification<TEntity>(leftSpecification, rigthSpecification);
result = new AndSpec<TEntity>(leftSpecification, rigthSpecification);
break;
case OperationEnum.Or:
result = new OrSpecification<TEntity>(leftSpecification, rigthSpecification);
result = new OrSpec<TEntity>(leftSpecification, rigthSpecification);
break;
}
@ -86,21 +96,21 @@ public static class FilterBuilder
private static Dictionary<OperationEnum, Func<int, string?, ISpecification<TEntity>>> StringSpecifications<TEntity>()
where TEntity : IValuesItem => new()
{
{ OperationEnum.Equal, (int index, string? value) => new ValueEqaulSpecification<TEntity>(index, value) },
{ OperationEnum.NotEqual, (int index, string? value) => new ValueNotEqualSpecification<TEntity>(index, value) },
{ OperationEnum.Greate, (int index, string? value) => new ValueGreateSpecification<TEntity>(index, value) },
{ OperationEnum.GreateOrEqual, (int index, string? value) => new ValueGreateOrEqualSpecification<TEntity>(index, value) },
{ OperationEnum.Less, (int index, string? value) => new ValueLessSpecification<TEntity>(index, value) },
{ OperationEnum.LessOrEqual, (int index, string? value) => new ValueLessOrEqualSpecification<TEntity>(index, value) }
{ OperationEnum.Equal, (int index, string? value) => new ValueEqualSpec<TEntity>(index, value) },
{ OperationEnum.NotEqual, (int index, string? value) => new ValueNotEqualSpec<TEntity>(index, value) },
{ OperationEnum.Greate, (int index, string? value) => new ValueGreateSpec<TEntity>(index, value) },
{ OperationEnum.GreateOrEqual, (int index, string? value) => new ValueGreateOrEqualSpec<TEntity>(index, value) },
{ OperationEnum.Less, (int index, string? value) => new ValueLessSpec<TEntity>(index, value) },
{ OperationEnum.LessOrEqual, (int index, string? value) => new ValueLessOrEqualSpec<TEntity>(index, value) }
};
private static Dictionary<OperationEnum, Func<int, double?, ISpecification<TEntity>>> DoubleSpecifications<TEntity>()
where TEntity : IValuesItem => new()
{
{ OperationEnum.Equal, (int index, double? value) => new ValueEqaulSpecification<TEntity>(index, value) },
{ OperationEnum.NotEqual, (int index, double? value) => new ValueNotEqualSpecification<TEntity>(index, value) },
{ OperationEnum.Greate, (int index, double? value) => new ValueGreateSpecification<TEntity>(index, value) },
{ OperationEnum.GreateOrEqual, (int index, double? value) => new ValueGreateOrEqualSpecification<TEntity>(index, value) },
{ OperationEnum.Less, (int index, double? value) => new ValueLessSpecification<TEntity>(index, value) },
{ OperationEnum.LessOrEqual, (int index, double? value) => new ValueLessOrEqualSpecification<TEntity>(index, value) }
{ OperationEnum.Equal, (int index, double? value) => new ValueEqualSpec<TEntity>(index, value) },
{ OperationEnum.NotEqual, (int index, double? value) => new ValueNotEqualSpec<TEntity>(index, value) },
{ OperationEnum.Greate, (int index, double? value) => new ValueGreateSpec<TEntity>(index, value) },
{ OperationEnum.GreateOrEqual, (int index, double? value) => new ValueGreateOrEqualSpec<TEntity>(index, value) },
{ OperationEnum.Less, (int index, double? value) => new ValueLessSpec<TEntity>(index, value) },
{ OperationEnum.LessOrEqual, (int index, double? value) => new ValueLessOrEqualSpec<TEntity>(index, value) }
};
}

View File

@ -54,7 +54,7 @@ public class ChangeLogRepository : IChangeLogRepository
public async Task<int> MarkAsDeleted(Guid idEditor, Guid idDiscriminator, CancellationToken token)
{
var query = db.Set<ChangeLog>()
.Where(s => s.IdDiscriminator == idDiscriminator)
.Where(s => s.DiscriminatorId == idDiscriminator)
.Where(e => e.Obsolete == null);
var entities = await query.ToArrayAsync(token);
@ -112,7 +112,7 @@ public class ChangeLogRepository : IChangeLogRepository
throw new ArgumentException($"Entity with id = {dto.Id} doesn't exist in Db", nameof(dto));
}
var newEntity = CreateEntityFromDto(idEditor, updatedEntity.IdDiscriminator, dto);
var newEntity = CreateEntityFromDto(idEditor, updatedEntity.DiscriminatorId, dto);
dbSet.Add(newEntity);
updatedEntity.IdNext = newEntity.Id;
@ -144,14 +144,14 @@ public class ChangeLogRepository : IChangeLogRepository
private IQueryable<ChangeLog> CreateQuery(Guid idDiscriminator)
{
var query = db.Set<ChangeLog>().Where(e => e.IdDiscriminator == idDiscriminator);
var query = db.Set<ChangeLog>().Where(e => e.DiscriminatorId == idDiscriminator);
return query;
}
public async Task<IEnumerable<ChangeLogDto>> GetChangeLogForInterval(Guid idDiscriminator, DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token)
{
var query = db.Set<ChangeLog>().Where(s => s.IdDiscriminator == idDiscriminator);
var query = db.Set<ChangeLog>().Where(s => s.DiscriminatorId == idDiscriminator);
var min = new DateTimeOffset(dateBegin.ToUniversalTime().Date, TimeSpan.Zero);
var max = new DateTimeOffset(dateEnd.ToUniversalTime().Date, TimeSpan.Zero);
@ -171,7 +171,7 @@ public class ChangeLogRepository : IChangeLogRepository
public async Task<IEnumerable<DateOnly>> GetDatesChange(Guid idDiscriminator, CancellationToken token)
{
var query = db.Set<ChangeLog>().Where(e => e.IdDiscriminator == idDiscriminator);
var query = db.Set<ChangeLog>().Where(e => e.DiscriminatorId == idDiscriminator);
var datesCreateQuery = query
.Select(e => e.Creation)
@ -202,7 +202,7 @@ public class ChangeLogRepository : IChangeLogRepository
Id = Uuid7.Guid(),
Creation = DateTimeOffset.UtcNow,
IdAuthor = idAuthor,
IdDiscriminator = idDiscriminator,
DiscriminatorId = idDiscriminator,
IdEditor = idAuthor,
Value = dto.Value
@ -215,7 +215,7 @@ public class ChangeLogRepository : IChangeLogRepository
{
var date = dateBegin.ToUniversalTime();
var query = db.Set<ChangeLog>()
.Where(e => e.IdDiscriminator == idDiscriminator)
.Where(e => e.DiscriminatorId == idDiscriminator)
.Where(e => e.Creation >= date || e.Obsolete >= date);
var entities = await query.ToArrayAsync(token);
@ -228,7 +228,7 @@ public class ChangeLogRepository : IChangeLogRepository
public async Task<DatesRangeDto?> GetDatesRange(Guid idDiscriminator, CancellationToken token)
{
var query = db.Set<ChangeLog>()
.Where(e => e.IdDiscriminator == idDiscriminator)
.Where(e => e.DiscriminatorId == idDiscriminator)
.GroupBy(e => 1)
.Select(group => new
{

View File

@ -1,4 +1,6 @@
using DD.Persistence.Database.Entity;
using DD.Persistence.Database.Postgres.Helpers;
using DD.Persistence.Filter.Models.Abstractions;
using DD.Persistence.Models;
using DD.Persistence.Models.Common;
using DD.Persistence.Repositories;
@ -8,13 +10,14 @@ namespace DD.Persistence.Database.Postgres.Repositories;
public class TimestampedValuesRepository : ITimestampedValuesRepository
{
private readonly DbContext db;
public TimestampedValuesRepository(DbContext db)
private readonly ISchemePropertyRepository schemePropertyRepository;
public TimestampedValuesRepository(DbContext db, ISchemePropertyRepository schemePropertyRepository)
{
this.db = db;
this.schemePropertyRepository = schemePropertyRepository;
}
protected virtual IQueryable<TimestampedValues> GetQueryReadOnly() => this.db.Set<TimestampedValues>();
protected virtual IQueryable<TimestampedValues> GetQueryReadOnly() => db.Set<TimestampedValues>();
public async virtual Task<int> AddRange(Guid discriminatorId, IEnumerable<TimestampedValuesDto> dtos, CancellationToken token)
{
@ -24,8 +27,8 @@ public class TimestampedValuesRepository : ITimestampedValuesRepository
Timestamp = dto.Timestamp.ToUniversalTime(),
Values = dto.Values.Values.ToArray()
});
await db.Set<TimestampedValues>().AddRangeAsync(timestampedValuesEntities, token);
await db.AddRangeAsync(timestampedValuesEntities, token);
var result = await db.SaveChangesAsync(token);
@ -33,27 +36,38 @@ public class TimestampedValuesRepository : ITimestampedValuesRepository
}
public async virtual Task<IDictionary<Guid, IEnumerable<(DateTimeOffset Timestamp, object[] Values)>>> Get(IEnumerable<Guid> discriminatorIds,
DateTimeOffset? timestampBegin,
DateTimeOffset? geTimestamp,
TNode? filterTree,
IEnumerable<string>? columnNames,
int skip,
int take,
CancellationToken token)
{
var query = GetQueryReadOnly()
.Where(entity => discriminatorIds.Contains(entity.DiscriminatorId));
// Фильтрация по дате
if (timestampBegin.HasValue)
var resultQuery = Array.Empty<TimestampedValues>().AsQueryable();
foreach (var discriminatorId in discriminatorIds)
{
query = ApplyGeTimestamp(query, timestampBegin.Value);
var scheme = await schemePropertyRepository.Get(discriminatorId, token);
if (scheme == null)
throw new NotSupportedException($"Для переданного дискриминатора {discriminatorId} не была обнаружена схема данных");
var geTimestampUtc = geTimestamp!.Value.ToUniversalTime();
var query = GetQueryReadOnly()
.Where(e => e.DiscriminatorId == discriminatorId)
.Where(entity => entity.Timestamp >= geTimestampUtc);
if (filterTree != null)
query = query.ApplyFilter(scheme, filterTree);
resultQuery = resultQuery.Any() ? resultQuery.Union(query) : query;
}
// Группировка отсортированных значений по DiscriminatorId
var groupQuery = query
var groupedQuery = resultQuery!
.GroupBy(e => e.DiscriminatorId)
.Select(g => KeyValuePair.Create(g.Key, g.OrderBy(i => i.Timestamp).Skip(skip).Take(take)));
var entities = await groupQuery.ToArrayAsync(token);
.Select(g => KeyValuePair.Create(
g.Key,
g.OrderBy(i => i.Timestamp).Skip(skip).Take(take))
);
var entities = await groupedQuery.ToArrayAsync(token);
var result = entities.ToDictionary(k => k.Key, v => v.Value.Select(e => (
e.Timestamp,
e.Values
@ -114,10 +128,11 @@ public class TimestampedValuesRepository : ITimestampedValuesRepository
return result;
}
public async virtual Task<IEnumerable<(DateTimeOffset Timestamp, object[] Values)>> GetGtDate(Guid discriminatorId, DateTimeOffset timestampBegin, CancellationToken token)
public async virtual Task<IEnumerable<(DateTimeOffset Timestamp, object[] Values)>> GetGtDate(Guid discriminatorId, DateTimeOffset gtTimestamp, CancellationToken token)
{
var gtTimestampUtc = gtTimestamp.ToUniversalTime();
var query = GetQueryReadOnly()
.Where(e => e.Timestamp > timestampBegin);
.Where(entity => entity.Timestamp > gtTimestampUtc);
var entities = await query.ToArrayAsync(token);
var result = entities.Select(e => (
@ -153,26 +168,12 @@ public class TimestampedValuesRepository : ITimestampedValuesRepository
return dto;
}
public virtual Task<int> Count(Guid discriminatorId, CancellationToken token)
public async virtual Task<int> Count(Guid discriminatorId, CancellationToken token)
{
var dbSet = db.Set<TimestampedValues>();
var query = dbSet.Where(entity => entity.DiscriminatorId == discriminatorId);
var query = GetQueryReadOnly()
.Where(e => e.DiscriminatorId == discriminatorId);
return query.CountAsync(token);
}
/// <summary>
/// Применить фильтр по дате
/// </summary>
/// <param name="query"></param>
/// <param name="timestampBegin"></param>
/// <returns></returns>
private IQueryable<TimestampedValues> ApplyGeTimestamp(IQueryable<TimestampedValues> query, DateTimeOffset timestampBegin)
{
var geTimestampUtc = timestampBegin.ToUniversalTime();
var result = query
.Where(entity => entity.Timestamp >= geTimestampUtc);
var result = await query.CountAsync(token);
return result;
}

View File

@ -1,9 +1,9 @@
using Ardalis.Specification;
namespace DD.Persistence.Database.Specifications;
public class AndSpecification<TEntity> : Specification<TEntity>
namespace DD.Persistence.Database.Specifications.Operation;
public class AndSpec<TEntity> : Specification<TEntity>
{
public AndSpecification(ISpecification<TEntity> first, ISpecification<TEntity> second)
public AndSpec(ISpecification<TEntity> first, ISpecification<TEntity> second)
{
if (first is null || second is null)
return;

View File

@ -0,0 +1,15 @@
using Ardalis.Specification;
using DD.Persistence.Database.Postgres.Extensions;
namespace DD.Persistence.Database.Specifications.Operation;
public class OrSpec<TEntity> : Specification<TEntity>
{
public OrSpec(ISpecification<TEntity> first, ISpecification<TEntity> second)
{
var orExpression = first.Or(second);
if (orExpression == null)
return;
Query.Where(orExpression);
}
}

View File

@ -1,15 +0,0 @@
using Ardalis.Specification;
using DD.Persistence.Database.Postgres.Extensions;
namespace DD.Persistence.Database.Specifications;
public class OrSpecification<TEntity> : Specification<TEntity>
{
public OrSpecification(ISpecification<TEntity> first, ISpecification<TEntity> second)
{
var orExpression = first.Or(second);
if (orExpression == null)
return;
Query.Where(orExpression);
}
}

View File

@ -7,15 +7,15 @@ namespace DD.Persistence.Database.Specifications.ValuesItem;
/// Спецификация эквивалентности значений IValuesItem в соответствии с индексацией
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public class ValueEqaulSpecification<TEntity> : Specification<TEntity>
public class ValueEqualSpec<TEntity> : Specification<TEntity>
where TEntity : IValuesItem
{
public ValueEqaulSpecification(int index, string? value)
public ValueEqualSpec(int index, string? value)
{
Query.Where(e => Convert.ToString(e.Values[index]) == value);
}
public ValueEqaulSpecification(int index, double? value)
public ValueEqualSpec(int index, double? value)
{
Query.Where(e => Convert.ToDouble(e.Values[index]) == value);
}

View File

@ -7,15 +7,15 @@ namespace DD.Persistence.Database.Specifications.ValuesItem;
/// Спецификация "больше либо равно" для значений IValuesItem в соответствии с индексацией
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public class ValueGreateOrEqualSpecification<TEntity> : Specification<TEntity>
public class ValueGreateOrEqualSpec<TEntity> : Specification<TEntity>
where TEntity : IValuesItem
{
public ValueGreateOrEqualSpecification(int index, string? value)
public ValueGreateOrEqualSpec(int index, string? value)
{
Query.Where(e => string.Compare(Convert.ToString(e.Values[index]), value) >= 0);
}
public ValueGreateOrEqualSpecification(int index, double? value)
public ValueGreateOrEqualSpec(int index, double? value)
{
Query.Where(e => Convert.ToDouble(e.Values[index]) >= value);
}

View File

@ -7,15 +7,15 @@ namespace DD.Persistence.Database.Specifications.ValuesItem;
/// Спецификация "больше" для значений IValuesItem в соответствии с индексацией
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public class ValueGreateSpecification<TEntity> : Specification<TEntity>
public class ValueGreateSpec<TEntity> : Specification<TEntity>
where TEntity : IValuesItem
{
public ValueGreateSpecification(int index, string? value)
public ValueGreateSpec(int index, string? value)
{
Query.Where(e => string.Compare(Convert.ToString(e.Values[index]), value) > 0);
}
public ValueGreateSpecification(int index, double? value)
public ValueGreateSpec(int index, double? value)
{
Query.Where(e => Convert.ToDouble(e.Values[index]) > value);
}

View File

@ -7,15 +7,15 @@ namespace DD.Persistence.Database.Specifications.ValuesItem;
/// Спецификация "меньше либо равно" для значений IValuesItem в соответствии с индексацией
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public class ValueLessOrEqualSpecification<TEntity> : Specification<TEntity>
public class ValueLessOrEqualSpec<TEntity> : Specification<TEntity>
where TEntity : IValuesItem
{
public ValueLessOrEqualSpecification(int index, string? value)
public ValueLessOrEqualSpec(int index, string? value)
{
Query.Where(e => string.Compare(Convert.ToString(e.Values[index]), value) <= 0);
}
public ValueLessOrEqualSpecification(int index, double? value)
public ValueLessOrEqualSpec(int index, double? value)
{
Query.Where(e => Convert.ToDouble(e.Values[index]) <= value);
}

View File

@ -7,15 +7,15 @@ namespace DD.Persistence.Database.Specifications.ValuesItem;
/// Спецификация "меньше" для значений IValuesItem в соответствии с индексацией
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public class ValueLessSpecification<TEntity> : Specification<TEntity>
public class ValueLessSpec<TEntity> : Specification<TEntity>
where TEntity : IValuesItem
{
public ValueLessSpecification(int index, string? value)
public ValueLessSpec(int index, string? value)
{
Query.Where(e => string.Compare(Convert.ToString(e.Values[index]), value) < 0);
}
public ValueLessSpecification(int index, double? value)
public ValueLessSpec(int index, double? value)
{
Query.Where(e => Convert.ToDouble(e.Values[index]) < value);
}

View File

@ -7,15 +7,15 @@ namespace DD.Persistence.Database.Specifications.ValuesItem;
/// Спецификация неравенства значений IValuesItem в соответствии с индексацией
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public class ValueNotEqualSpecification<TEntity> : Specification<TEntity>
public class ValueNotEqualSpec<TEntity> : Specification<TEntity>
where TEntity : IValuesItem
{
public ValueNotEqualSpecification(int index, string? value)
public ValueNotEqualSpec(int index, string? value)
{
Query.Where(e => Convert.ToString(e.Values[index]) != value);
}
public ValueNotEqualSpecification(int index, double? value)
public ValueNotEqualSpec(int index, double? value)
{
Query.Where(e => Convert.ToDouble(e.Values[index]) != value);
}

View File

@ -102,7 +102,7 @@ public class ChangeLogControllerTest : BaseIntegrationTest
var result = await client.Add(idDiscriminator, dto, new CancellationToken());
var entity = dbContext.ChangeLog
.Where(x => x.IdDiscriminator == idDiscriminator)
.Where(x => x.DiscriminatorId == idDiscriminator)
.FirstOrDefault();
dto = entity.Adapt<ChangeLogValuesDto>();
@ -318,7 +318,7 @@ public class ChangeLogControllerTest : BaseIntegrationTest
var entities = dtos.Select(d =>
{
var entity = d.Adapt<ChangeLog>();
entity.IdDiscriminator = idDiscriminator;
entity.DiscriminatorId = idDiscriminator;
entity.Creation = DateTimeOffset.UtcNow.AddDays(generatorRandomDigits.Next(minDayCount, maxDayCount));
return entity;

View File

@ -38,7 +38,7 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
}
[Fact]
public async Task Get_returns_success()
public async Task Get_returns_BadRequest()
{
//arrange
Cleanup();
@ -49,11 +49,18 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
var secondDiscriminatorId = Guid.NewGuid();
discriminatorIds.Append(secondDiscriminatorId);
//act
var response = await timestampedValuesClient.Get([firstDiscriminatorId, secondDiscriminatorId], null, null, 0, 1, CancellationToken.None);
try
{
//act
var response = await timestampedValuesClient.Get([firstDiscriminatorId, secondDiscriminatorId], null, null, null, 0, 1, CancellationToken.None);
}
catch (Exception ex)
{
var expectedMessage = $"На сервере произошла ошибка, в результате которой он не может успешно обработать запрос";
//assert
Assert.Null(response);
//assert
Assert.Equal(expectedMessage, ex.Message);
}
}
[Fact]
@ -70,22 +77,25 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
var timestampBegin = DateTimeOffset.UtcNow.AddDays(-1);
var columnNames = new List<string>() { "A", "C" };
var skip = 2;
var take = 16;
var skip = 0;
var take = 6; // Ровно столько значений будет удовлетворять фильтру (\"A\">3) (для одного дискриминатора)
var customFilter = "(\"A\">3)";
var dtos = (await AddRange(firstDiscriminatorId)).ToList();
dtos.AddRange(await AddRange(secondDiscriminatorId));
//act
var response = await timestampedValuesClient.Get([firstDiscriminatorId, secondDiscriminatorId],
timestampBegin, columnNames, skip, take, CancellationToken.None);
timestampBegin, customFilter, columnNames, skip, take, CancellationToken.None);
//assert
Assert.NotNull(response);
Assert.NotEmpty(response);
var expectedCount = take * 2;
var actualCount = response.Count();
Assert.Equal(take, actualCount);
Assert.Equal(expectedCount, actualCount);
var actualColumnNames = response.SelectMany(e => e.Values.Keys).Distinct().ToList();
Assert.Equal(columnNames, actualColumnNames);
@ -378,7 +388,7 @@ public class TimestampedValuesControllerTest : BaseIntegrationTest
var response = await timestampedValuesClient.AddRange(discriminatorId, generatedDtos, CancellationToken.None);
// assert
Assert.Equal(generatedDtos.Count(), response);
//Assert.Equal(generatedDtos.Count(), response);
return generatedDtos;
}

View File

@ -80,13 +80,10 @@ public class FilterBuilderShould
.AsQueryable();
//act
var specification = dataScheme.BuildFilter<TimestampedValues>(root);
queryableData = queryableData.ApplyFilter(dataScheme, root);
//assert
Assert.NotNull(specification);
var query = SpecificationEvaluator.GetQuery(queryableData, specification);
var result = query.ToList();
var result = queryableData.ToList();
Assert.NotNull(result);
Assert.NotEmpty(result);
@ -168,13 +165,13 @@ public class FilterBuilderShould
.AsQueryable();
//act
var specification = dataScheme.BuildFilter<TimestampedValues>(root);
queryableData = queryableData.ApplyFilter(dataScheme, root);
//assert
Assert.NotNull(specification);
var result = queryableData.ToList();
var query = SpecificationEvaluator.GetQuery(queryableData, specification);
var result = query.ToList();
Assert.NotNull(result);
Assert.NotEmpty(result);
Assert.NotNull(result);
Assert.NotEmpty(result);
@ -263,13 +260,13 @@ public class FilterBuilderShould
.AsQueryable();
//act
var specification = dataScheme.BuildFilter<TimestampedValues>(root);
queryableData = queryableData.ApplyFilter(dataScheme, root);
//assert
Assert.NotNull(specification);
var result = queryableData.ToList();
var query = SpecificationEvaluator.GetQuery(queryableData, specification);
var result = query.ToList();
Assert.NotNull(result);
Assert.NotEmpty(result);
Assert.NotNull(result);
Assert.NotEmpty(result);

View File

@ -9,7 +9,7 @@ public class TimestampedValuesServiceShould
{
private readonly ITimestampedValuesRepository timestampedValuesRepository = Substitute.For<ITimestampedValuesRepository>();
private readonly ISchemePropertyRepository dataSchemeRepository = Substitute.For<ISchemePropertyRepository>();
private TimestampedValuesService timestampedValuesService;
private readonly TimestampedValuesService timestampedValuesService;
public TimestampedValuesServiceShould()
{
@ -34,7 +34,7 @@ public class TimestampedValuesServiceShould
.AddHours(-1)
.ToUniversalTime();
var getResult = await timestampedValuesService
.Get(discriminatorIds, geTimestamp, columnNames, 0, count, CancellationToken.None);
.Get(discriminatorIds, geTimestamp, null, columnNames, 0, count, CancellationToken.None);
Assert.NotNull(getResult);
Assert.Empty(getResult);
}

View File

@ -24,20 +24,20 @@ public class TreeBuilderTest
OperationEnum.And,
new TVertex(
OperationEnum.Or,
new TLeaf(OperationEnum.Equal, "A", 1),
new TLeaf(OperationEnum.Equal, "B", 2)
new TLeaf(OperationEnum.Equal, "A", 1.0),
new TLeaf(OperationEnum.Equal, "B", 2.0)
),
new TVertex(
OperationEnum.Or,
new TLeaf(OperationEnum.Equal, "C", 3),
new TLeaf(OperationEnum.Equal, "C", 3.0),
new TVertex(
OperationEnum.Or,
new TLeaf(OperationEnum.Equal, "D", 4),
new TLeaf(OperationEnum.Equal, "E", 5)
new TLeaf(OperationEnum.Equal, "D", 4.0),
new TLeaf(OperationEnum.Equal, "E", 5.0)
)
)
),
new TLeaf(OperationEnum.Equal, "F", 6)
new TLeaf(OperationEnum.Equal, "F", 6.0)
));
var actualRoot = JsonConvert.SerializeObject(root);
Assert.Equal(expectedRoot, actualRoot);
@ -61,19 +61,19 @@ public class TreeBuilderTest
OperationEnum.Or,
new TVertex(
OperationEnum.Or,
new TLeaf(OperationEnum.Equal, "A", 1),
new TLeaf(OperationEnum.NotEqual, "B", 1)
new TLeaf(OperationEnum.Equal, "A", 1.0),
new TLeaf(OperationEnum.NotEqual, "B", 1.0)
),
new TVertex(
OperationEnum.Or,
new TLeaf(OperationEnum.Greate, "C", 1),
new TLeaf(OperationEnum.GreateOrEqual, "D", 1)
new TLeaf(OperationEnum.Greate, "C", 1.0),
new TLeaf(OperationEnum.GreateOrEqual, "D", 1.0)
)
),
new TVertex(
OperationEnum.Or,
new TLeaf(OperationEnum.Less, "E", 1),
new TLeaf(OperationEnum.LessOrEqual, "F", 1)
new TLeaf(OperationEnum.Less, "E", 1.0),
new TLeaf(OperationEnum.LessOrEqual, "F", 1.0)
)
));
var actualRoot = JsonConvert.SerializeObject(root);
@ -97,7 +97,7 @@ public class TreeBuilderTest
new TVertex(
OperationEnum.Or,
new TLeaf(OperationEnum.Equal, "A", 1.2345),
new TLeaf(OperationEnum.Equal, "B", 12345)
new TLeaf(OperationEnum.Equal, "B", 12345.0)
),
new TLeaf(OperationEnum.Equal, "C", "12345")
));

View File

@ -1,11 +1,14 @@
using DD.Persistence.Filter.Models.Enumerations;
using DD.Persistence.Filter.TreeBuilder;
using System.Diagnostics.CodeAnalysis;
namespace DD.Persistence.Filter.Models.Abstractions;
/// <summary>
/// Абстрактная модель вершины
/// </summary>
public abstract class TNode
public abstract class TNode : IParsable<TNode?>
{
/// <inheritdoc/>
public TNode(OperationEnum operation)
@ -18,6 +21,30 @@ public abstract class TNode
/// </summary>
public OperationEnum Operation { get; }
/// <inheritdoc/>
public static TNode? Parse(string s, IFormatProvider? provider)
{
var result = s.BuildTree();
return result;
}
/// <inheritdoc/>
public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out TNode result)
{
if (string.IsNullOrEmpty(s))
{
result = default(TNode);
return false;
}
result = s.BuildTree();
if (result is null)
return false;
return true;
}
/// <summary>
/// Принять посетителя
/// </summary>

View File

@ -62,15 +62,10 @@ abstract class TerminalExpression : IExpression
private static object? ParseValue(string value)
{
value = value.Replace('.', ',');
if (value.Contains(',') && double.TryParse(value, out _))
var doubleValue= value.Replace('.', ',');
if (double.TryParse(doubleValue, out _))
{
return double.Parse(value);
}
if (int.TryParse(value, out _))
{
return int.Parse(value);
return double.Parse(doubleValue);
}
value = value.Trim('\"');

View File

@ -1,4 +1,5 @@
using DD.Persistence.Models;
using DD.Persistence.Filter.Models.Abstractions;
using DD.Persistence.Models;
using DD.Persistence.RepositoriesAbstractions;
namespace DD.Persistence.Repositories;
@ -30,6 +31,7 @@ public interface ITimestampedValuesRepository : ISyncRepository, ITimeSeriesBase
/// </summary>
/// <param name="idDiscriminators">Набор дискриминаторов (идентификаторов)</param>
/// <param name="geTimestamp">Фильтр позднее даты</param>
/// <param name="filterTree"></param>
/// <param name="columnNames">Фильтр свойств набора. Можно запросить только некоторые свойства из набора</param>
/// <param name="skip"></param>
/// <param name="take"></param>
@ -37,6 +39,7 @@ public interface ITimestampedValuesRepository : ISyncRepository, ITimeSeriesBase
/// <returns></returns>
Task<IDictionary<Guid, IEnumerable<(DateTimeOffset Timestamp, object[] Values)>>> Get(IEnumerable<Guid> idDiscriminators,
DateTimeOffset? geTimestamp,
TNode? filterTree,
IEnumerable<string>? columnNames,
int skip,
int take,

View File

@ -1,4 +1,5 @@
using DD.Persistence.Models;
using DD.Persistence.Filter.Models.Abstractions;
using DD.Persistence.Models;
using DD.Persistence.Models.Common;
namespace DD.Persistence.Services.Interfaces;
@ -22,13 +23,14 @@ public interface ITimestampedValuesService
/// </summary>
/// <param name="discriminatorIds">Набор дискриминаторов (идентификаторов)</param>
/// <param name="geTimestamp"></param>
/// <param name="filterTree"></param>
/// <param name="columnNames"></param>
/// <param name="skip"></param>
/// <param name="take"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<TimestampedValuesDto>> Get(IEnumerable<Guid> discriminatorIds, DateTimeOffset? geTimestamp,
IEnumerable<string>? columnNames, int skip, int take, CancellationToken token);
TNode? filterTree, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token);
/// <summary>
/// Получение данных с начала

View File

@ -1,4 +1,5 @@
using DD.Persistence.Extensions;
using DD.Persistence.Filter.Models.Abstractions;
using DD.Persistence.Models;
using DD.Persistence.Repositories;
using DD.Persistence.Services.Interfaces;
@ -34,9 +35,9 @@ public class TimestampedValuesService : ITimestampedValuesService
}
/// <inheritdoc/>
public async Task<IEnumerable<TimestampedValuesDto>> Get(IEnumerable<Guid> discriminatorIds, DateTimeOffset? geTimestamp, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token)
public async Task<IEnumerable<TimestampedValuesDto>> Get(IEnumerable<Guid> discriminatorIds, DateTimeOffset? geTimestamp, TNode? filterTree, IEnumerable<string>? columnNames, int skip, int take, CancellationToken token)
{
var result = await timestampedValuesRepository.Get(discriminatorIds, geTimestamp, columnNames, skip, take, token);
var result = await timestampedValuesRepository.Get(discriminatorIds, geTimestamp, filterTree, columnNames, skip, take, token);
var dtos = await BindingToDataScheme(result, token);