#959 Реализовать обход бинарного дерева и создание фильтра на основе спецификаций #26
@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
@ -7,6 +7,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Ardalis.Specification.EntityFrameworkCore" Version="8.0.0" />
|
||||||
<PackageReference Include="Mapster" Version="7.4.0" />
|
<PackageReference Include="Mapster" Version="7.4.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0">
|
||||||
|
@ -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
|
public class TimestampedValues : ITimestampedItem, IValuesItem
|
||||||
{
|
{
|
||||||
[Comment("Временная отметка"), Key]
|
[Comment("Временная отметка"), Key]
|
||||||
public DateTimeOffset Timestamp { get; set; }
|
public DateTimeOffset Timestamp { get; set; }
|
||||||
|
14
DD.Persistence.Database/EntityAbstractions/IValuesItem.cs
Normal file
14
DD.Persistence.Database/EntityAbstractions/IValuesItem.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using DD.Persistence.Database.Entity;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Database.EntityAbstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Сущность с данными, принадлежащими к определенной схеме
|
||||||
|
/// </summary>
|
||||||
|
public interface IValuesItem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Значения
|
||||||
|
/// </summary>
|
||||||
|
object[] Values { get; set; }
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
using Ardalis.Specification;
|
||||||
|
using DD.Persistence.Database.Helpers;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Database.Postgres.Extensions;
|
||||||
|
|
||||||
|
public static class SpecificationExtensions
|
||||||
|
{
|
||||||
|
public static Expression<Func<T, bool>>? Or<T>(this ISpecification<T> spec, ISpecification<T> otherSpec)
|
||||||
|
{
|
||||||
|
var parameter = Expression.Parameter(typeof(T), "x");
|
||||||
|
|
||||||
|
var exprSpec1 = CombineWhereExpressions(spec.WhereExpressions, parameter);
|
||||||
|
var exprSpec2 = CombineWhereExpressions(otherSpec.WhereExpressions, parameter);
|
||||||
|
|
||||||
|
Expression? orExpression = exprSpec1 is not null && exprSpec2 is not null
|
||||||
|
? Expression.OrElse(exprSpec1, exprSpec2)
|
||||||
|
: exprSpec1 ?? exprSpec2;
|
||||||
|
|
||||||
|
if (orExpression is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var lambdaExpr = Expression.Lambda<Func<T, bool>>(orExpression, parameter);
|
||||||
|
|
||||||
|
return lambdaExpr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Expression? CombineWhereExpressions<T>(IEnumerable<WhereExpressionInfo<T>> whereExpressions, ParameterExpression parameter)
|
||||||
|
{
|
||||||
|
Expression? newExpr = null;
|
||||||
|
foreach (var where in whereExpressions)
|
||||||
|
{
|
||||||
|
var expr = ParameterReplacerVisitor.Replace(where.Filter.Body, where.Filter.Parameters[0], parameter);
|
||||||
|
newExpr = newExpr is null ? expr : Expression.AndAlso(newExpr, expr);
|
||||||
|
}
|
||||||
|
return newExpr;
|
||||||
|
}
|
||||||
|
}
|
106
DD.Persistence.Database/Helpers/FilterBuilder.cs
Normal file
106
DD.Persistence.Database/Helpers/FilterBuilder.cs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
using Ardalis.Specification;
|
||||||
|
using DD.Persistence.Database.EntityAbstractions;
|
||||||
|
using DD.Persistence.Database.Postgres.Extensions;
|
||||||
|
using DD.Persistence.Database.Specifications;
|
||||||
|
using DD.Persistence.Database.Specifications.ValuesItem;
|
||||||
|
using DD.Persistence.Filter.Models;
|
||||||
|
using DD.Persistence.Filter.Models.Abstractions;
|
||||||
|
using DD.Persistence.Filter.Models.Enumerations;
|
||||||
|
using DD.Persistence.Filter.Visitors;
|
||||||
|
using DD.Persistence.Models;
|
||||||
|
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
|
||||||
|
{
|
||||||
|
var result = dataSchemeDto.BuildSpecificationByNextNode<TEntity>(root);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ISpecification<TEntity>? BuildSpecificationByNextNode<TEntity>(this DataSchemeDto dataSchemeDto, TNode node)
|
||||||
|
where TEntity : IValuesItem
|
||||||
|
{
|
||||||
|
var visitor = new NodeVisitor<ISpecification<TEntity>?>(
|
||||||
|
dataSchemeDto.VertexProcessing<TEntity>,
|
||||||
|
dataSchemeDto.LeafProcessing<TEntity>
|
||||||
|
);
|
||||||
|
|
||||||
|
var result = node.AcceptVisitor(visitor);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ISpecification<TEntity>? VertexProcessing<TEntity>(this DataSchemeDto dataSchemeDto, TVertex vertex)
|
||||||
|
where TEntity : IValuesItem
|
||||||
|
{
|
||||||
|
var leftSpecification = dataSchemeDto.BuildSpecificationByNextNode<TEntity>(vertex.Left);
|
||||||
|
var rigthSpecification = dataSchemeDto.BuildSpecificationByNextNode<TEntity>(vertex.Rigth);
|
||||||
|
if (leftSpecification is null)
|
||||||
|
return rigthSpecification;
|
||||||
|
if (rigthSpecification is null)
|
||||||
|
return leftSpecification;
|
||||||
|
|
||||||
|
ISpecification<TEntity>? result = null;
|
||||||
|
switch (vertex.Operation)
|
||||||
|
{
|
||||||
|
case OperationEnum.And:
|
||||||
|
result = new AndSpecification<TEntity>(leftSpecification, rigthSpecification);
|
||||||
|
break;
|
||||||
|
case OperationEnum.Or:
|
||||||
|
result = new OrSpecification<TEntity>(leftSpecification, rigthSpecification);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ISpecification<TEntity>? LeafProcessing<TEntity>(this DataSchemeDto dataSchemeDto, TLeaf leaf)
|
||||||
|
where TEntity : IValuesItem
|
||||||
|
{
|
||||||
|
var schemeProperty = dataSchemeDto.FirstOrDefault(e => e.PropertyName.Equals(leaf.PropName));
|
||||||
|
if (schemeProperty is null)
|
||||||
|
throw new ArgumentException($"Свойство {leaf.PropName} не найдено в схеме данных");
|
||||||
|
|
||||||
|
ISpecification<TEntity>? result = null;
|
||||||
|
switch (schemeProperty.PropertyKind)
|
||||||
|
{
|
||||||
|
case JsonValueKind.String:
|
||||||
|
var stringValue = Convert.ToString(leaf.Value);
|
||||||
|
var stringSpecifications = StringSpecifications<TEntity>();
|
||||||
|
result = stringSpecifications[leaf.Operation](schemeProperty.Index, stringValue);
|
||||||
|
break;
|
||||||
|
case JsonValueKind.Number:
|
||||||
|
var doubleValue = Convert.ToDouble(leaf.Value);
|
||||||
|
var doubleSpecifications = DoubleSpecifications<TEntity>();
|
||||||
|
result = doubleSpecifications[leaf.Operation](schemeProperty.Index, doubleValue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) }
|
||||||
|
};
|
||||||
|
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) }
|
||||||
|
};
|
||||||
|
}
|
20
DD.Persistence.Database/Helpers/ParameterReplacerVisitor.cs
Normal file
20
DD.Persistence.Database/Helpers/ParameterReplacerVisitor.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Database.Helpers;
|
||||||
|
public class ParameterReplacerVisitor : ExpressionVisitor
|
||||||
|
{
|
||||||
|
private readonly Expression _newExpression;
|
||||||
|
private readonly ParameterExpression _oldParameter;
|
||||||
|
|
||||||
|
private ParameterReplacerVisitor(ParameterExpression oldParameter, Expression newExpression)
|
||||||
|
{
|
||||||
|
_oldParameter = oldParameter;
|
||||||
|
_newExpression = newExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static Expression Replace(Expression expression, ParameterExpression oldParameter, Expression newExpression)
|
||||||
|
=> new ParameterReplacerVisitor(oldParameter, newExpression).Visit(expression);
|
||||||
|
|
||||||
|
protected override Expression VisitParameter(ParameterExpression p)
|
||||||
|
=> p == _oldParameter ? _newExpression : p;
|
||||||
|
}
|
@ -23,7 +23,6 @@ public static class QueryBuilders
|
|||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static async Task<PaginationContainer<TDto>> ApplyPagination<TEntity, TDto>(
|
public static async Task<PaginationContainer<TDto>> ApplyPagination<TEntity, TDto>(
|
||||||
this IQueryable<TEntity> query,
|
this IQueryable<TEntity> query,
|
||||||
PaginationRequest request,
|
PaginationRequest request,
|
||||||
|
22
DD.Persistence.Database/Specifications/AndSpecification.cs
Normal file
22
DD.Persistence.Database/Specifications/AndSpecification.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using Ardalis.Specification;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Database.Specifications;
|
||||||
|
public class AndSpecification<TEntity> : Specification<TEntity>
|
||||||
|
{
|
||||||
|
public AndSpecification(ISpecification<TEntity> first, ISpecification<TEntity> second)
|
||||||
|
{
|
||||||
|
if (first is null || second is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ApplyCriteria(first);
|
||||||
|
ApplyCriteria(second);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyCriteria(ISpecification<TEntity> specification)
|
||||||
|
{
|
||||||
|
foreach (var criteria in specification.WhereExpressions)
|
||||||
|
{
|
||||||
|
Query.Where(criteria.Filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
DD.Persistence.Database/Specifications/OrSpecification.cs
Normal file
15
DD.Persistence.Database/Specifications/OrSpecification.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
using Ardalis.Specification;
|
||||||
|
using DD.Persistence.Database.EntityAbstractions;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Database.Specifications.ValuesItem;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Спецификация эквивалентности значений IValuesItem в соответствии с индексацией
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity"></typeparam>
|
||||||
|
public class ValueEqaulSpecification<TEntity> : Specification<TEntity>
|
||||||
|
where TEntity : IValuesItem
|
||||||
|
{
|
||||||
|
public ValueEqaulSpecification(int index, string? value)
|
||||||
|
{
|
||||||
|
Query.Where(e => Convert.ToString(e.Values[index]) == value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueEqaulSpecification(int index, double? value)
|
||||||
|
{
|
||||||
|
Query.Where(e => Convert.ToDouble(e.Values[index]) == value);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
using Ardalis.Specification;
|
||||||
|
using DD.Persistence.Database.EntityAbstractions;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Database.Specifications.ValuesItem;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Спецификация "больше либо равно" для значений IValuesItem в соответствии с индексацией
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity"></typeparam>
|
||||||
|
public class ValueGreateOrEqualSpecification<TEntity> : Specification<TEntity>
|
||||||
|
where TEntity : IValuesItem
|
||||||
|
{
|
||||||
|
public ValueGreateOrEqualSpecification(int index, string? value)
|
||||||
|
{
|
||||||
|
Query.Where(e => string.Compare(Convert.ToString(e.Values[index]), value) >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueGreateOrEqualSpecification(int index, double? value)
|
||||||
|
{
|
||||||
|
Query.Where(e => Convert.ToDouble(e.Values[index]) >= value);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
using Ardalis.Specification;
|
||||||
|
using DD.Persistence.Database.EntityAbstractions;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Database.Specifications.ValuesItem;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Спецификация "больше" для значений IValuesItem в соответствии с индексацией
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity"></typeparam>
|
||||||
|
public class ValueGreateSpecification<TEntity> : Specification<TEntity>
|
||||||
|
where TEntity : IValuesItem
|
||||||
|
{
|
||||||
|
public ValueGreateSpecification(int index, string? value)
|
||||||
|
{
|
||||||
|
Query.Where(e => string.Compare(Convert.ToString(e.Values[index]), value) > 0);
|
||||||
|
|||||||
|
}
|
||||||
|
|
||||||
|
public ValueGreateSpecification(int index, double? value)
|
||||||
|
{
|
||||||
|
Query.Where(e => Convert.ToDouble(e.Values[index]) > value);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
using Ardalis.Specification;
|
||||||
|
using DD.Persistence.Database.EntityAbstractions;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Database.Specifications.ValuesItem;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Спецификация "меньше либо равно" для значений IValuesItem в соответствии с индексацией
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity"></typeparam>
|
||||||
|
public class ValueLessOrEqualSpecification<TEntity> : Specification<TEntity>
|
||||||
|
where TEntity : IValuesItem
|
||||||
|
{
|
||||||
|
public ValueLessOrEqualSpecification(int index, string? value)
|
||||||
|
{
|
||||||
|
Query.Where(e => string.Compare(Convert.ToString(e.Values[index]), value) <= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueLessOrEqualSpecification(int index, double? value)
|
||||||
|
{
|
||||||
|
Query.Where(e => Convert.ToDouble(e.Values[index]) <= value);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
using Ardalis.Specification;
|
||||||
|
using DD.Persistence.Database.EntityAbstractions;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Database.Specifications.ValuesItem;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Спецификация "меньше" для значений IValuesItem в соответствии с индексацией
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity"></typeparam>
|
||||||
|
public class ValueLessSpecification<TEntity> : Specification<TEntity>
|
||||||
|
where TEntity : IValuesItem
|
||||||
|
{
|
||||||
|
public ValueLessSpecification(int index, string? value)
|
||||||
|
{
|
||||||
|
Query.Where(e => string.Compare(Convert.ToString(e.Values[index]), value) < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueLessSpecification(int index, double? value)
|
||||||
|
{
|
||||||
|
Query.Where(e => Convert.ToDouble(e.Values[index]) < value);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
using Ardalis.Specification;
|
||||||
|
using DD.Persistence.Database.EntityAbstractions;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Database.Specifications.ValuesItem;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Спецификация неравенства значений IValuesItem в соответствии с индексацией
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity"></typeparam>
|
||||||
|
public class ValueNotEqualSpecification<TEntity> : Specification<TEntity>
|
||||||
|
where TEntity : IValuesItem
|
||||||
|
{
|
||||||
|
public ValueNotEqualSpecification(int index, string? value)
|
||||||
|
{
|
||||||
|
Query.Where(e => Convert.ToString(e.Values[index]) != value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueNotEqualSpecification(int index, double? value)
|
||||||
|
{
|
||||||
|
Query.Where(e => Convert.ToDouble(e.Values[index]) != value);
|
||||||
|
}
|
||||||
|
}
|
@ -16,8 +16,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\DD.Persistence.Database\DD.Persistence.Database.csproj" />
|
<ProjectReference Include="..\DD.Persistence.Database.Postgres\DD.Persistence.Database.Postgres.csproj" />
|
||||||
<ProjectReference Include="..\DD.Persistence\DD.Persistence.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
281
DD.Persistence.Test/FilterBuilderShould.cs
Normal file
281
DD.Persistence.Test/FilterBuilderShould.cs
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
using Ardalis.Specification.EntityFrameworkCore;
|
||||||
|
using DD.Persistence.Database.Entity;
|
||||||
|
using DD.Persistence.Filter.Models;
|
||||||
|
using DD.Persistence.Filter.Models.Enumerations;
|
||||||
|
using DD.Persistence.Models;
|
||||||
|
using DD.Persistence.Database.Postgres.Helpers;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace DD.Persistence.Test;
|
||||||
|
|
||||||
|
/// ToDo: переписать под Theory
|
||||||
|
public class FilterBuilderShould
|
||||||
|
{
|
||||||
|
private readonly SpecificationEvaluator SpecificationEvaluator;
|
||||||
|
public FilterBuilderShould()
|
||||||
|
{
|
||||||
|
this.SpecificationEvaluator = new SpecificationEvaluator();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TestFilterBuilding()
|
||||||
|
{
|
||||||
|
//arrange
|
||||||
|
var discriminatorId = Guid.NewGuid();
|
||||||
|
var dataSchemeProperties = new SchemePropertyDto[]
|
||||||
|
{
|
||||||
|
new SchemePropertyDto()
|
||||||
|
{
|
||||||
|
Index = 0,
|
||||||
|
PropertyName = "A",
|
||||||
|
PropertyKind = JsonValueKind.String
|
||||||
|
},
|
||||||
|
new SchemePropertyDto()
|
||||||
|
{
|
||||||
|
Index = 1,
|
||||||
|
PropertyName = "B",
|
||||||
|
PropertyKind = JsonValueKind.Number
|
||||||
|
},
|
||||||
|
new SchemePropertyDto()
|
||||||
|
{
|
||||||
|
Index = 2,
|
||||||
|
PropertyName = "C",
|
||||||
|
PropertyKind = JsonValueKind.String
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var dataScheme = new DataSchemeDto(discriminatorId, dataSchemeProperties);
|
||||||
|
var filterDate = DateTime.Now.AddMinutes(-1);
|
||||||
|
var root = new TVertex(
|
||||||
|
OperationEnum.Or,
|
||||||
|
new TVertex(
|
||||||
|
OperationEnum.And,
|
||||||
|
new TLeaf(OperationEnum.Greate, "A", filterDate),
|
||||||
|
new TLeaf(OperationEnum.Less, "B", 2.22)
|
||||||
|
),
|
||||||
|
new TLeaf(OperationEnum.Equal, "C", "IsEqualText")
|
||||||
|
);
|
||||||
|
var queryableData = new[]
|
||||||
|
{
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-1),
|
||||||
|
Values = new object[] { filterDate.AddMinutes(-1), 200, "IsEqualText" } // true
|
||||||
|
},
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-2),
|
||||||
|
Values = new object[] { filterDate.AddMinutes(1), 2.21, "IsNotEqualText" } // true
|
||||||
|
},
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-3),
|
||||||
|
Values = new object[] { filterDate.AddMinutes(-1), 2.22, "IsNotEqualText" } // false
|
||||||
|
},
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-4),
|
||||||
|
Values = new object[] { filterDate.AddMinutes(-1), 2.21, "IsNotEqualText" } // false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.AsQueryable();
|
||||||
|
|
||||||
|
//act
|
||||||
|
var specification = dataScheme.BuildFilter<TimestampedValues>(root);
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.NotNull(specification);
|
||||||
|
|
||||||
|
var query = SpecificationEvaluator.GetQuery(queryableData, specification);
|
||||||
|
var result = query.ToList();
|
||||||
|
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.NotEmpty(result);
|
||||||
|
|
||||||
|
var expectedCount = 2;
|
||||||
|
var actualCount = result.Count();
|
||||||
|
Assert.Equal(expectedCount, actualCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TestFilterOperations()
|
||||||
|
{
|
||||||
|
//arrange
|
||||||
|
var discriminatorId = Guid.NewGuid();
|
||||||
|
var dataSchemeProperties = new SchemePropertyDto[]
|
||||||
|
{
|
||||||
|
new SchemePropertyDto()
|
||||||
|
{
|
||||||
|
Index = 0,
|
||||||
|
PropertyName = "A",
|
||||||
|
PropertyKind = JsonValueKind.Number
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var dataScheme = new DataSchemeDto(discriminatorId, dataSchemeProperties);
|
||||||
|
var root = new TVertex(
|
||||||
|
OperationEnum.Or,
|
||||||
|
new TVertex(
|
||||||
|
OperationEnum.And,
|
||||||
|
new TVertex(
|
||||||
|
OperationEnum.And,
|
||||||
|
new TVertex(
|
||||||
|
OperationEnum.And,
|
||||||
|
new TVertex(
|
||||||
|
OperationEnum.And,
|
||||||
|
new TLeaf(OperationEnum.Less, "A", 2),
|
||||||
|
new TLeaf(OperationEnum.LessOrEqual, "A", 1.99)
|
||||||
|
),
|
||||||
|
new TLeaf(OperationEnum.GreateOrEqual, "A", 1.97)
|
||||||
|
),
|
||||||
|
new TLeaf(OperationEnum.Greate, "A", 1.96)
|
||||||
|
),
|
||||||
|
new TLeaf(OperationEnum.NotEqual, "A", 1.98)
|
||||||
|
),
|
||||||
|
new TLeaf(OperationEnum.Equal, "A", 1)
|
||||||
|
);
|
||||||
|
var queryableData = new[]
|
||||||
|
{
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-1),
|
||||||
|
Values = new object[] { 1 } // true
|
||||||
|
},
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-2),
|
||||||
|
Values = new object[] { 1.96 } // false
|
||||||
|
},
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-3),
|
||||||
|
Values = new object[] { 1.97 } // true
|
||||||
|
},
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-4),
|
||||||
|
Values = new object[] { 1.98 } // false
|
||||||
|
},
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-5),
|
||||||
|
Values = new object[] { 1.99 } // true
|
||||||
|
},
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-6),
|
||||||
|
Values = new object[] { 2 } // false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.AsQueryable();
|
||||||
|
|
||||||
|
//act
|
||||||
|
var specification = dataScheme.BuildFilter<TimestampedValues>(root);
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.NotNull(specification);
|
||||||
|
|
||||||
|
var query = SpecificationEvaluator.GetQuery(queryableData, specification);
|
||||||
|
var result = query.ToList();
|
||||||
|
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.NotEmpty(result);
|
||||||
|
|
||||||
|
var expectedCount = 3;
|
||||||
|
var actualCount = result.Count();
|
||||||
|
Assert.Equal(expectedCount, actualCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TestFilterValues()
|
||||||
|
{
|
||||||
|
//arrange
|
||||||
|
var discriminatorId = Guid.NewGuid();
|
||||||
|
var filterDate = DateTimeOffset.Now;
|
||||||
|
var dataSchemeProperties = new SchemePropertyDto[]
|
||||||
|
{
|
||||||
|
new SchemePropertyDto()
|
||||||
|
{
|
||||||
|
Index = 0,
|
||||||
|
PropertyName = "A",
|
||||||
|
PropertyKind = JsonValueKind.Number
|
||||||
|
},
|
||||||
|
new SchemePropertyDto()
|
||||||
|
{
|
||||||
|
Index = 1,
|
||||||
|
PropertyName = "B",
|
||||||
|
PropertyKind = JsonValueKind.Number
|
||||||
|
},
|
||||||
|
new SchemePropertyDto()
|
||||||
|
{
|
||||||
|
Index = 2,
|
||||||
|
PropertyName = "C",
|
||||||
|
PropertyKind = JsonValueKind.String
|
||||||
|
},
|
||||||
|
new SchemePropertyDto()
|
||||||
|
{
|
||||||
|
Index = 3,
|
||||||
|
PropertyName = "D",
|
||||||
|
PropertyKind = JsonValueKind.String
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var dataScheme = new DataSchemeDto(discriminatorId, dataSchemeProperties);
|
||||||
|
|
||||||
|
var root = new TVertex(
|
||||||
|
OperationEnum.Or,
|
||||||
|
new TVertex(
|
||||||
|
OperationEnum.Or,
|
||||||
|
new TVertex(
|
||||||
|
OperationEnum.Or,
|
||||||
|
new TLeaf(OperationEnum.Equal, "A", 1),
|
||||||
|
new TLeaf(OperationEnum.Equal, "B", 1.11)
|
||||||
|
),
|
||||||
|
new TLeaf(OperationEnum.Equal, "C", "IsEqualText")
|
||||||
|
),
|
||||||
|
new TLeaf(OperationEnum.Equal, "D", filterDate)
|
||||||
|
);
|
||||||
|
var queryableData = new[]
|
||||||
|
{
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-1),
|
||||||
|
Values = new object[] { 1, 2.22, "IsNotEqualText", DateTimeOffset.Now.AddMinutes(-1) } // true
|
||||||
|
},
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-2),
|
||||||
|
Values = new object[] { 2, 1.11, "IsNotEqualText", DateTimeOffset.Now.AddMinutes(-1) } // true
|
||||||
|
},
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-3),
|
||||||
|
Values = new object[] { 2, 2.22, "IsEqualText", DateTimeOffset.Now.AddMinutes(-1) } // true
|
||||||
|
},
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-4),
|
||||||
|
Values = new object[] { 2, 2.22, "IsNotEqualText", filterDate } // true
|
||||||
|
},
|
||||||
|
new TimestampedValues {
|
||||||
|
DiscriminatorId = discriminatorId,
|
||||||
|
Timestamp = DateTimeOffset.Now.AddMinutes(-1),
|
||||||
|
Values = new object[] { 2, 2.22, "IsNotEqualText", DateTimeOffset.Now.AddMinutes(-1) } // false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.AsQueryable();
|
||||||
|
|
||||||
|
//act
|
||||||
|
var specification = dataScheme.BuildFilter<TimestampedValues>(root);
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.NotNull(specification);
|
||||||
|
|
||||||
|
var query = SpecificationEvaluator.GetQuery(queryableData, specification);
|
||||||
|
var result = query.ToList();
|
||||||
|
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.NotEmpty(result);
|
||||||
|
|
||||||
|
var expectedCount = 4;
|
||||||
|
var actualCount = result.Count();
|
||||||
|
Assert.Equal(expectedCount, actualCount);
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,15 @@
|
|||||||
using DD.Persistence.Models;
|
using DD.Persistence.Models;
|
||||||
using DD.Persistence.Repositories;
|
using DD.Persistence.Repositories;
|
||||||
using DD.Persistence.Services;
|
using DD.Persistence.Services;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace DD.Persistence.Test;
|
namespace DD.Persistence.Test;
|
||||||
public class TimestampedValuesServiceShould
|
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 readonly TimestampedValuesService timestampedValuesService;
|
private TimestampedValuesService timestampedValuesService;
|
||||||
|
|
||||||
public TimestampedValuesServiceShould()
|
public TimestampedValuesServiceShould()
|
||||||
{
|
{
|
||||||
@ -44,10 +45,10 @@ public class TimestampedValuesServiceShould
|
|||||||
{
|
{
|
||||||
var values = new Dictionary<string, object>()
|
var values = new Dictionary<string, object>()
|
||||||
{
|
{
|
||||||
{ "A", i },
|
{ "A", GetJsonFromObject(i) },
|
||||||
{ "B", i * 1.1 },
|
{ "B", GetJsonFromObject(i * 1.1) },
|
||||||
{ "C", $"Any{i}" },
|
{ "C", GetJsonFromObject($"Any{i}") },
|
||||||
{ "D", DateTimeOffset.Now },
|
{ "D", GetJsonFromObject(DateTimeOffset.Now) }
|
||||||
};
|
};
|
||||||
|
|
||||||
yield return new TimestampedValuesDto()
|
yield return new TimestampedValuesDto()
|
||||||
@ -57,4 +58,11 @@ public class TimestampedValuesServiceShould
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static JsonElement GetJsonFromObject(object value)
|
||||||
|
{
|
||||||
|
var jsonString = JsonSerializer.Serialize(value);
|
||||||
|
var doc = JsonDocument.Parse(jsonString);
|
||||||
|
return doc.RootElement;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user
Все методы Convert.To...() нормально мапятся в SQL?