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.Models.Common;
using DD.Persistence.Repositories; using DD.Persistence.Repositories;
using DD.Persistence.Services.Interfaces; using DD.Persistence.Services.Interfaces;
@ -45,6 +46,7 @@ public class TimestampedValuesController : ControllerBase
/// </summary> /// </summary>
/// <param name="discriminatorIds">Набор дискриминаторов</param> /// <param name="discriminatorIds">Набор дискриминаторов</param>
/// <param name="timestampBegin">Фильтр позднее даты</param> /// <param name="timestampBegin">Фильтр позднее даты</param>
/// <param name="filterTree">Кастомный фильтр по набору значений</param>
/// <param name="columnNames">Фильтр свойств набора</param> /// <param name="columnNames">Фильтр свойств набора</param>
/// <param name="skip"></param> /// <param name="skip"></param>
/// <param name="take"></param> /// <param name="take"></param>
@ -52,9 +54,14 @@ public class TimestampedValuesController : ControllerBase
[HttpGet] [HttpGet]
[ProducesResponseType(typeof(IEnumerable<TimestampedValuesDto>), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(IEnumerable<TimestampedValuesDto>), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NoContent)] [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(); 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.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models; 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 Swashbuckle.AspNetCore.SwaggerGen;
using System.Reflection; using System.Reflection;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using DD.Persistence.Database.Entity;
namespace DD.Persistence.API; namespace DD.Persistence.API;
@ -30,6 +28,7 @@ public static class DependencyInjection
new OpenApiSchema {Type = "number", Format = "float" } new OpenApiSchema {Type = "number", Format = "float" }
] ]
}); });
c.MapType<TNode>(() => new OpenApiSchema { Type = "string" });
c.CustomOperationIds(e => c.CustomOperationIds(e =>
{ {

View File

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

View File

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

View File

@ -32,17 +32,17 @@ public class TimestampedValuesClient : BaseClient, ITimestampedValuesClient
} }
/// <inheritdoc/> /// <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( 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!; return result!;
} }
/// <inheritdoc/> /// <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); var mapper = GetMapper<T>(discriminatorId);
return data.Select(mapper.DeserializeTimeStampedData); return data.Select(mapper.DeserializeTimeStampedData);

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@ namespace DD.Persistence.Database.Entity;
[Table("parameter_data")] [Table("parameter_data")]
[PrimaryKey(nameof(DiscriminatorId), nameof(ParameterId), nameof(Timestamp))] [PrimaryKey(nameof(DiscriminatorId), nameof(ParameterId), nameof(Timestamp))]
public class ParameterData : ITimestampedItem public class ParameterData : IDiscriminatorItem, ITimestampedItem
{ {
[Required, Comment("Дискриминатор системы")] [Required, Comment("Дискриминатор системы")]
public Guid DiscriminatorId { get; set; } 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;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json; using System.Text.Json;
@ -7,7 +8,7 @@ namespace DD.Persistence.Database.Entity;
[Table("scheme_property")] [Table("scheme_property")]
[PrimaryKey(nameof(DiscriminatorId), nameof(Index))] [PrimaryKey(nameof(DiscriminatorId), nameof(Index))]
public class SchemeProperty public class SchemeProperty : IDiscriminatorItem
{ {
[Comment("Идентификатор схемы данных")] [Comment("Идентификатор схемы данных")]
public Guid DiscriminatorId { get; set; } public Guid DiscriminatorId { get; set; }

View File

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

View File

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

View File

@ -38,7 +38,7 @@ public interface IChangeLog
/// <summary> /// <summary>
/// Дискриминатор таблицы /// Дискриминатор таблицы
/// </summary> /// </summary>
public Guid IdDiscriminator { get; set; } public Guid DiscriminatorId { get; set; }
/// <summary> /// <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;
using Ardalis.Specification.EntityFrameworkCore;
using DD.Persistence.Database.Entity;
using DD.Persistence.Database.EntityAbstractions; using DD.Persistence.Database.EntityAbstractions;
using DD.Persistence.Database.Postgres.Extensions; using DD.Persistence.Database.Specifications.Operation;
using DD.Persistence.Database.Specifications;
using DD.Persistence.Database.Specifications.ValuesItem; using DD.Persistence.Database.Specifications.ValuesItem;
using DD.Persistence.Filter.Models; using DD.Persistence.Filter.Models;
using DD.Persistence.Filter.Models.Abstractions; using DD.Persistence.Filter.Models.Abstractions;
@ -13,8 +14,17 @@ using System.Text.Json;
namespace DD.Persistence.Database.Postgres.Helpers; namespace DD.Persistence.Database.Postgres.Helpers;
public static class FilterBuilder public static class FilterBuilder
{ {
public static ISpecification<TEntity>? BuildFilter<TEntity>(this DataSchemeDto dataSchemeDto, TNode root) public static IQueryable<TEntity> ApplyFilter<TEntity>(this IQueryable<TEntity> query, DataSchemeDto dataSchemeDto, TNode root)
where TEntity : IValuesItem 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); var result = dataSchemeDto.BuildSpecificationByNextNode<TEntity>(root);
@ -48,10 +58,10 @@ public static class FilterBuilder
switch (vertex.Operation) switch (vertex.Operation)
{ {
case OperationEnum.And: case OperationEnum.And:
result = new AndSpecification<TEntity>(leftSpecification, rigthSpecification); result = new AndSpec<TEntity>(leftSpecification, rigthSpecification);
break; break;
case OperationEnum.Or: case OperationEnum.Or:
result = new OrSpecification<TEntity>(leftSpecification, rigthSpecification); result = new OrSpec<TEntity>(leftSpecification, rigthSpecification);
break; break;
} }
@ -86,21 +96,21 @@ public static class FilterBuilder
private static Dictionary<OperationEnum, Func<int, string?, ISpecification<TEntity>>> StringSpecifications<TEntity>() private static Dictionary<OperationEnum, Func<int, string?, ISpecification<TEntity>>> StringSpecifications<TEntity>()
where TEntity : IValuesItem => new() where TEntity : IValuesItem => new()
{ {
{ OperationEnum.Equal, (int index, string? value) => new ValueEqaulSpecification<TEntity>(index, value) }, { OperationEnum.Equal, (int index, string? value) => new ValueEqualSpec<TEntity>(index, value) },
{ OperationEnum.NotEqual, (int index, string? value) => new ValueNotEqualSpecification<TEntity>(index, value) }, { OperationEnum.NotEqual, (int index, string? value) => new ValueNotEqualSpec<TEntity>(index, value) },
{ OperationEnum.Greate, (int index, string? value) => new ValueGreateSpecification<TEntity>(index, value) }, { OperationEnum.Greate, (int index, string? value) => new ValueGreateSpec<TEntity>(index, value) },
{ OperationEnum.GreateOrEqual, (int index, string? value) => new ValueGreateOrEqualSpecification<TEntity>(index, value) }, { OperationEnum.GreateOrEqual, (int index, string? value) => new ValueGreateOrEqualSpec<TEntity>(index, value) },
{ OperationEnum.Less, (int index, string? value) => new ValueLessSpecification<TEntity>(index, value) }, { OperationEnum.Less, (int index, string? value) => new ValueLessSpec<TEntity>(index, value) },
{ OperationEnum.LessOrEqual, (int index, string? value) => new ValueLessOrEqualSpecification<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>() private static Dictionary<OperationEnum, Func<int, double?, ISpecification<TEntity>>> DoubleSpecifications<TEntity>()
where TEntity : IValuesItem => new() where TEntity : IValuesItem => new()
{ {
{ OperationEnum.Equal, (int index, double? value) => new ValueEqaulSpecification<TEntity>(index, value) }, { OperationEnum.Equal, (int index, double? value) => new ValueEqualSpec<TEntity>(index, value) },
{ OperationEnum.NotEqual, (int index, double? value) => new ValueNotEqualSpecification<TEntity>(index, value) }, { OperationEnum.NotEqual, (int index, double? value) => new ValueNotEqualSpec<TEntity>(index, value) },
{ OperationEnum.Greate, (int index, double? value) => new ValueGreateSpecification<TEntity>(index, value) }, { OperationEnum.Greate, (int index, double? value) => new ValueGreateSpec<TEntity>(index, value) },
{ OperationEnum.GreateOrEqual, (int index, double? value) => new ValueGreateOrEqualSpecification<TEntity>(index, value) }, { OperationEnum.GreateOrEqual, (int index, double? value) => new ValueGreateOrEqualSpec<TEntity>(index, value) },
{ OperationEnum.Less, (int index, double? value) => new ValueLessSpecification<TEntity>(index, value) }, { OperationEnum.Less, (int index, double? value) => new ValueLessSpec<TEntity>(index, value) },
{ OperationEnum.LessOrEqual, (int index, double? value) => new ValueLessOrEqualSpecification<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) public async Task<int> MarkAsDeleted(Guid idEditor, Guid idDiscriminator, CancellationToken token)
{ {
var query = db.Set<ChangeLog>() var query = db.Set<ChangeLog>()
.Where(s => s.IdDiscriminator == idDiscriminator) .Where(s => s.DiscriminatorId == idDiscriminator)
.Where(e => e.Obsolete == null); .Where(e => e.Obsolete == null);
var entities = await query.ToArrayAsync(token); 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)); 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); dbSet.Add(newEntity);
updatedEntity.IdNext = newEntity.Id; updatedEntity.IdNext = newEntity.Id;
@ -144,14 +144,14 @@ public class ChangeLogRepository : IChangeLogRepository
private IQueryable<ChangeLog> CreateQuery(Guid idDiscriminator) 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; return query;
} }
public async Task<IEnumerable<ChangeLogDto>> GetChangeLogForInterval(Guid idDiscriminator, DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token) 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 min = new DateTimeOffset(dateBegin.ToUniversalTime().Date, TimeSpan.Zero);
var max = new DateTimeOffset(dateEnd.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) 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 var datesCreateQuery = query
.Select(e => e.Creation) .Select(e => e.Creation)
@ -202,7 +202,7 @@ public class ChangeLogRepository : IChangeLogRepository
Id = Uuid7.Guid(), Id = Uuid7.Guid(),
Creation = DateTimeOffset.UtcNow, Creation = DateTimeOffset.UtcNow,
IdAuthor = idAuthor, IdAuthor = idAuthor,
IdDiscriminator = idDiscriminator, DiscriminatorId = idDiscriminator,
IdEditor = idAuthor, IdEditor = idAuthor,
Value = dto.Value Value = dto.Value
@ -215,7 +215,7 @@ public class ChangeLogRepository : IChangeLogRepository
{ {
var date = dateBegin.ToUniversalTime(); var date = dateBegin.ToUniversalTime();
var query = db.Set<ChangeLog>() var query = db.Set<ChangeLog>()
.Where(e => e.IdDiscriminator == idDiscriminator) .Where(e => e.DiscriminatorId == idDiscriminator)
.Where(e => e.Creation >= date || e.Obsolete >= date); .Where(e => e.Creation >= date || e.Obsolete >= date);
var entities = await query.ToArrayAsync(token); var entities = await query.ToArrayAsync(token);
@ -228,7 +228,7 @@ public class ChangeLogRepository : IChangeLogRepository
public async Task<DatesRangeDto?> GetDatesRange(Guid idDiscriminator, CancellationToken token) public async Task<DatesRangeDto?> GetDatesRange(Guid idDiscriminator, CancellationToken token)
{ {
var query = db.Set<ChangeLog>() var query = db.Set<ChangeLog>()
.Where(e => e.IdDiscriminator == idDiscriminator) .Where(e => e.DiscriminatorId == idDiscriminator)
.GroupBy(e => 1) .GroupBy(e => 1)
.Select(group => new .Select(group => new
{ {

View File

@ -1,4 +1,6 @@
using DD.Persistence.Database.Entity; 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;
using DD.Persistence.Models.Common; using DD.Persistence.Models.Common;
using DD.Persistence.Repositories; using DD.Persistence.Repositories;
@ -8,13 +10,14 @@ namespace DD.Persistence.Database.Postgres.Repositories;
public class TimestampedValuesRepository : ITimestampedValuesRepository public class TimestampedValuesRepository : ITimestampedValuesRepository
{ {
private readonly DbContext db; private readonly DbContext db;
private readonly ISchemePropertyRepository schemePropertyRepository;
public TimestampedValuesRepository(DbContext db) public TimestampedValuesRepository(DbContext db, ISchemePropertyRepository schemePropertyRepository)
{ {
this.db = db; 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) 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(), Timestamp = dto.Timestamp.ToUniversalTime(),
Values = dto.Values.Values.ToArray() Values = dto.Values.Values.ToArray()
}); });
await db.Set<TimestampedValues>().AddRangeAsync(timestampedValuesEntities, token); await db.AddRangeAsync(timestampedValuesEntities, token);
var result = await db.SaveChangesAsync(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, public async virtual Task<IDictionary<Guid, IEnumerable<(DateTimeOffset Timestamp, object[] Values)>>> Get(IEnumerable<Guid> discriminatorIds,
DateTimeOffset? timestampBegin, DateTimeOffset? geTimestamp,
TNode? filterTree,
IEnumerable<string>? columnNames, IEnumerable<string>? columnNames,
int skip, int skip,
int take, int take,
CancellationToken token) CancellationToken token)
{ {
var query = GetQueryReadOnly() var resultQuery = Array.Empty<TimestampedValues>().AsQueryable();
.Where(entity => discriminatorIds.Contains(entity.DiscriminatorId)); foreach (var discriminatorId in discriminatorIds)
// Фильтрация по дате
if (timestampBegin.HasValue)
{ {
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;
} }
var groupedQuery = resultQuery!
// Группировка отсортированных значений по DiscriminatorId
var groupQuery = query
.GroupBy(e => e.DiscriminatorId) .GroupBy(e => e.DiscriminatorId)
.Select(g => KeyValuePair.Create(g.Key, g.OrderBy(i => i.Timestamp).Skip(skip).Take(take))); .Select(g => KeyValuePair.Create(
var entities = await groupQuery.ToArrayAsync(token); 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 => ( var result = entities.ToDictionary(k => k.Key, v => v.Value.Select(e => (
e.Timestamp, e.Timestamp,
e.Values e.Values
@ -114,10 +128,11 @@ public class TimestampedValuesRepository : ITimestampedValuesRepository
return result; 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() var query = GetQueryReadOnly()
.Where(e => e.Timestamp > timestampBegin); .Where(entity => entity.Timestamp > gtTimestampUtc);
var entities = await query.ToArrayAsync(token); var entities = await query.ToArrayAsync(token);
var result = entities.Select(e => ( var result = entities.Select(e => (
@ -153,26 +168,12 @@ public class TimestampedValuesRepository : ITimestampedValuesRepository
return dto; 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 = GetQueryReadOnly()
var query = dbSet.Where(entity => entity.DiscriminatorId == discriminatorId); .Where(e => e.DiscriminatorId == discriminatorId);
return query.CountAsync(token); var result = await 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);
return result; return result;
} }

View File

@ -1,9 +1,9 @@
using Ardalis.Specification; using Ardalis.Specification;
namespace DD.Persistence.Database.Specifications; namespace DD.Persistence.Database.Specifications.Operation;
public class AndSpecification<TEntity> : Specification<TEntity> 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) if (first is null || second is null)
return; 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 в соответствии с индексацией /// Спецификация эквивалентности значений IValuesItem в соответствии с индексацией
/// </summary> /// </summary>
/// <typeparam name="TEntity"></typeparam> /// <typeparam name="TEntity"></typeparam>
public class ValueEqaulSpecification<TEntity> : Specification<TEntity> public class ValueEqualSpec<TEntity> : Specification<TEntity>
where TEntity : IValuesItem where TEntity : IValuesItem
{ {
public ValueEqaulSpecification(int index, string? value) public ValueEqualSpec(int index, string? value)
{ {
Query.Where(e => Convert.ToString(e.Values[index]) == 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); Query.Where(e => Convert.ToDouble(e.Values[index]) == value);
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -7,15 +7,15 @@ namespace DD.Persistence.Database.Specifications.ValuesItem;
/// Спецификация неравенства значений IValuesItem в соответствии с индексацией /// Спецификация неравенства значений IValuesItem в соответствии с индексацией
/// </summary> /// </summary>
/// <typeparam name="TEntity"></typeparam> /// <typeparam name="TEntity"></typeparam>
public class ValueNotEqualSpecification<TEntity> : Specification<TEntity> public class ValueNotEqualSpec<TEntity> : Specification<TEntity>
where TEntity : IValuesItem where TEntity : IValuesItem
{ {
public ValueNotEqualSpecification(int index, string? value) public ValueNotEqualSpec(int index, string? value)
{ {
Query.Where(e => Convert.ToString(e.Values[index]) != 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); 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 result = await client.Add(idDiscriminator, dto, new CancellationToken());
var entity = dbContext.ChangeLog var entity = dbContext.ChangeLog
.Where(x => x.IdDiscriminator == idDiscriminator) .Where(x => x.DiscriminatorId == idDiscriminator)
.FirstOrDefault(); .FirstOrDefault();
dto = entity.Adapt<ChangeLogValuesDto>(); dto = entity.Adapt<ChangeLogValuesDto>();
@ -318,7 +318,7 @@ public class ChangeLogControllerTest : BaseIntegrationTest
var entities = dtos.Select(d => var entities = dtos.Select(d =>
{ {
var entity = d.Adapt<ChangeLog>(); var entity = d.Adapt<ChangeLog>();
entity.IdDiscriminator = idDiscriminator; entity.DiscriminatorId = idDiscriminator;
entity.Creation = DateTimeOffset.UtcNow.AddDays(generatorRandomDigits.Next(minDayCount, maxDayCount)); entity.Creation = DateTimeOffset.UtcNow.AddDays(generatorRandomDigits.Next(minDayCount, maxDayCount));
return entity; return entity;

View File

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

View File

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

View File

@ -9,7 +9,7 @@ public class TimestampedValuesServiceShould
{ {
private readonly ITimestampedValuesRepository timestampedValuesRepository = Substitute.For<ITimestampedValuesRepository>(); private readonly ITimestampedValuesRepository timestampedValuesRepository = Substitute.For<ITimestampedValuesRepository>();
private readonly ISchemePropertyRepository dataSchemeRepository = Substitute.For<ISchemePropertyRepository>(); private readonly ISchemePropertyRepository dataSchemeRepository = Substitute.For<ISchemePropertyRepository>();
private TimestampedValuesService timestampedValuesService; private readonly TimestampedValuesService timestampedValuesService;
public TimestampedValuesServiceShould() public TimestampedValuesServiceShould()
{ {
@ -34,7 +34,7 @@ public class TimestampedValuesServiceShould
.AddHours(-1) .AddHours(-1)
.ToUniversalTime(); .ToUniversalTime();
var getResult = await timestampedValuesService 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.NotNull(getResult);
Assert.Empty(getResult); Assert.Empty(getResult);
} }

View File

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

View File

@ -1,11 +1,14 @@
using DD.Persistence.Filter.Models.Enumerations; using DD.Persistence.Filter.Models.Enumerations;
using DD.Persistence.Filter.TreeBuilder;
using System.Diagnostics.CodeAnalysis;
namespace DD.Persistence.Filter.Models.Abstractions; namespace DD.Persistence.Filter.Models.Abstractions;
/// <summary> /// <summary>
/// Абстрактная модель вершины /// Абстрактная модель вершины
/// </summary> /// </summary>
public abstract class TNode
public abstract class TNode : IParsable<TNode?>
{ {
/// <inheritdoc/> /// <inheritdoc/>
public TNode(OperationEnum operation) public TNode(OperationEnum operation)
@ -18,6 +21,30 @@ public abstract class TNode
/// </summary> /// </summary>
public OperationEnum Operation { get; } 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>
/// Принять посетителя /// Принять посетителя
/// </summary> /// </summary>

View File

@ -62,15 +62,10 @@ abstract class TerminalExpression : IExpression
private static object? ParseValue(string value) private static object? ParseValue(string value)
{ {
value = value.Replace('.', ','); var doubleValue= value.Replace('.', ',');
if (value.Contains(',') && double.TryParse(value, out _)) if (double.TryParse(doubleValue, out _))
{ {
return double.Parse(value); return double.Parse(doubleValue);
}
if (int.TryParse(value, out _))
{
return int.Parse(value);
} }
value = value.Trim('\"'); 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; using DD.Persistence.RepositoriesAbstractions;
namespace DD.Persistence.Repositories; namespace DD.Persistence.Repositories;
@ -30,6 +31,7 @@ public interface ITimestampedValuesRepository : ISyncRepository, ITimeSeriesBase
/// </summary> /// </summary>
/// <param name="idDiscriminators">Набор дискриминаторов (идентификаторов)</param> /// <param name="idDiscriminators">Набор дискриминаторов (идентификаторов)</param>
/// <param name="geTimestamp">Фильтр позднее даты</param> /// <param name="geTimestamp">Фильтр позднее даты</param>
/// <param name="filterTree"></param>
/// <param name="columnNames">Фильтр свойств набора. Можно запросить только некоторые свойства из набора</param> /// <param name="columnNames">Фильтр свойств набора. Можно запросить только некоторые свойства из набора</param>
/// <param name="skip"></param> /// <param name="skip"></param>
/// <param name="take"></param> /// <param name="take"></param>
@ -37,6 +39,7 @@ public interface ITimestampedValuesRepository : ISyncRepository, ITimeSeriesBase
/// <returns></returns> /// <returns></returns>
Task<IDictionary<Guid, IEnumerable<(DateTimeOffset Timestamp, object[] Values)>>> Get(IEnumerable<Guid> idDiscriminators, Task<IDictionary<Guid, IEnumerable<(DateTimeOffset Timestamp, object[] Values)>>> Get(IEnumerable<Guid> idDiscriminators,
DateTimeOffset? geTimestamp, DateTimeOffset? geTimestamp,
TNode? filterTree,
IEnumerable<string>? columnNames, IEnumerable<string>? columnNames,
int skip, int skip,
int take, 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; using DD.Persistence.Models.Common;
namespace DD.Persistence.Services.Interfaces; namespace DD.Persistence.Services.Interfaces;
@ -22,13 +23,14 @@ public interface ITimestampedValuesService
/// </summary> /// </summary>
/// <param name="discriminatorIds">Набор дискриминаторов (идентификаторов)</param> /// <param name="discriminatorIds">Набор дискриминаторов (идентификаторов)</param>
/// <param name="geTimestamp"></param> /// <param name="geTimestamp"></param>
/// <param name="filterTree"></param>
/// <param name="columnNames"></param> /// <param name="columnNames"></param>
/// <param name="skip"></param> /// <param name="skip"></param>
/// <param name="take"></param> /// <param name="take"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<TimestampedValuesDto>> Get(IEnumerable<Guid> discriminatorIds, DateTimeOffset? geTimestamp, 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> /// <summary>
/// Получение данных с начала /// Получение данных с начала

View File

@ -1,4 +1,5 @@
using DD.Persistence.Extensions; using DD.Persistence.Extensions;
using DD.Persistence.Filter.Models.Abstractions;
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Repositories; using DD.Persistence.Repositories;
using DD.Persistence.Services.Interfaces; using DD.Persistence.Services.Interfaces;
@ -34,9 +35,9 @@ public class TimestampedValuesService : ITimestampedValuesService
} }
/// <inheritdoc/> /// <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); var dtos = await BindingToDataScheme(result, token);