Реализовать обход бинарного дерева и создание фильтра на основе спецификаций
This commit is contained in:
parent
87264fd8db
commit
4b5477207d
@ -0,0 +1,83 @@
|
||||
using Ardalis.Specification;
|
||||
using DD.Persistence.Database.Specifications;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace DD.Persistence.Database.Postgres.Extensions;
|
||||
|
||||
// ToDo: рассмотреть возможность вынести логику в спецификации
|
||||
public static class SpecificationExtensions
|
||||
{
|
||||
public static ISpecification<T> Or<T>(this ISpecification<T> spec, ISpecification<T> otherSpec)
|
||||
{
|
||||
var newSpec = new EmptySpecification<T>();
|
||||
|
||||
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 newSpec;
|
||||
|
||||
var lambdaExpr = Expression.Lambda<Func<T, bool>>(orExpression, parameter);
|
||||
|
||||
newSpec.Query.Where(lambdaExpr);
|
||||
|
||||
return newSpec;
|
||||
}
|
||||
|
||||
// ToDo: Рефакторинг
|
||||
public static ISpecification<T> And<T>(this ISpecification<T> spec, ISpecification<T> otherSpec)
|
||||
{
|
||||
var newSpec = new EmptySpecification<T>();
|
||||
|
||||
var parameter = Expression.Parameter(typeof(T), "x");
|
||||
|
||||
var exprSpec1 = CombineWhereExpressions(spec.WhereExpressions, parameter);
|
||||
var exprSpec2 = CombineWhereExpressions(otherSpec.WhereExpressions, parameter);
|
||||
|
||||
Expression? andExpression = exprSpec1 is not null && exprSpec2 is not null
|
||||
? Expression.AndAlso(exprSpec1, exprSpec2)
|
||||
: exprSpec1 ?? exprSpec2;
|
||||
|
||||
if (andExpression is null) return newSpec;
|
||||
|
||||
var lambdaExpr = Expression.Lambda<Func<T, bool>>(andExpression, parameter);
|
||||
|
||||
newSpec.Query.Where(lambdaExpr);
|
||||
|
||||
return newSpec;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
137
DD.Persistence.Database.Postgres/Helpers/FilterBuilder.cs
Normal file
137
DD.Persistence.Database.Postgres/Helpers/FilterBuilder.cs
Normal file
@ -0,0 +1,137 @@
|
||||
using Ardalis.Specification;
|
||||
using DD.Persistence.Database.Entity;
|
||||
using DD.Persistence.Database.EntityAbstractions;
|
||||
using DD.Persistence.Database.Postgres.Extensions;
|
||||
using DD.Persistence.Database.Specifications.ValuesItem;
|
||||
using DD.Persistence.Filter.Models.Abstractions;
|
||||
using DD.Persistence.Filter.Models.Enumerations;
|
||||
using DD.Persistence.Filter.Visitors;
|
||||
using DD.Persistence.Models;
|
||||
|
||||
namespace DD.Persistence.Database.Postgres.Helpers;
|
||||
public static class FilterBuilder
|
||||
{
|
||||
public static ISpecification<TEntity>? BuildFilter<TEntity>(this DataScheme dataScheme, TNode root)
|
||||
where TEntity : IValuesItem
|
||||
{
|
||||
var result = dataScheme.BuildSpecificationByNextNode<TEntity>(root);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static ISpecification<TEntity>? BuildSpecificationByNextNode<TEntity>(this DataScheme dataScheme, TNode node)
|
||||
where TEntity : IValuesItem
|
||||
{
|
||||
var propIndexMap = dataScheme.PropNames
|
||||
.Select((name, index) => new { name, index })
|
||||
.ToDictionary(x => x.name, x => x.index);
|
||||
|
||||
var visitor = new NodeVisitor<ISpecification<TEntity>?>(
|
||||
v =>
|
||||
{
|
||||
var leftSpecification = dataScheme.BuildSpecificationByNextNode<TEntity>(v.Left);
|
||||
var rigthSpecification = dataScheme.BuildSpecificationByNextNode<TEntity>(v.Rigth);
|
||||
if (leftSpecification is null)
|
||||
return rigthSpecification;
|
||||
if (rigthSpecification is null)
|
||||
return leftSpecification;
|
||||
|
||||
ISpecification<TEntity>? result = null;
|
||||
switch (v.Operation)
|
||||
{
|
||||
case OperationEnum.And:
|
||||
result = leftSpecification.And(rigthSpecification);
|
||||
break;
|
||||
case OperationEnum.Or:
|
||||
result = leftSpecification.Or(rigthSpecification);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
t =>
|
||||
{
|
||||
int keyIndex;
|
||||
if (!propIndexMap.TryGetValue(t.PropName, out keyIndex))
|
||||
throw new ArgumentException($"Свойство {t.PropName} не найдено в схеме данных");
|
||||
var type = dataScheme.PropTypes[keyIndex];
|
||||
|
||||
ISpecification<TEntity>? result = null;
|
||||
switch (type)
|
||||
{
|
||||
case PropTypeEnum.String:
|
||||
var stringValue = Convert.ToString(t.Value);
|
||||
switch (t.Operation)
|
||||
{
|
||||
case OperationEnum.Equal:
|
||||
result = new ValueEqaulSpecification<TEntity>(keyIndex, stringValue);
|
||||
break;
|
||||
case OperationEnum.NotEqual:
|
||||
result = new ValueNotEqaulSpecification<TEntity>(keyIndex, stringValue);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case PropTypeEnum.Double:
|
||||
var doubleValue = Convert.ToDouble(t.Value);
|
||||
switch (t.Operation)
|
||||
// ToDo: можно схлопнуть в один Generic - метод, где TValue : struct
|
||||
// Но в таком случае придётся продумать аналогичное решение на уровне спецификаций
|
||||
{
|
||||
case OperationEnum.Equal:
|
||||
result = new ValueEqaulSpecification<TEntity>(keyIndex, doubleValue);
|
||||
break;
|
||||
case OperationEnum.NotEqual:
|
||||
result = new ValueNotEqaulSpecification<TEntity>(keyIndex, doubleValue);
|
||||
break;
|
||||
case OperationEnum.Greate:
|
||||
result = new ValueGreateSpecification<TEntity>(keyIndex, doubleValue);
|
||||
break;
|
||||
case OperationEnum.GreateOrEqual:
|
||||
result = new ValueGreateOrEqualSpecification<TEntity>(keyIndex, doubleValue);
|
||||
break;
|
||||
case OperationEnum.Less:
|
||||
result = new ValueLessSpecification<TEntity>(keyIndex, doubleValue);
|
||||
break;
|
||||
case OperationEnum.LessOrEqual:
|
||||
result = new ValueLessOrEqualSpecification<TEntity>(keyIndex, doubleValue);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case PropTypeEnum.DateTime:
|
||||
stringValue = Convert.ToString(t.Value);
|
||||
DateTimeOffset? dateTimeValue = string.IsNullOrEmpty(stringValue)
|
||||
? null
|
||||
: DateTimeOffset.Parse(stringValue);
|
||||
switch (t.Operation)
|
||||
{
|
||||
case OperationEnum.Equal:
|
||||
result = new ValueEqaulSpecification<TEntity>(keyIndex, dateTimeValue);
|
||||
break;
|
||||
case OperationEnum.NotEqual:
|
||||
result = new ValueNotEqaulSpecification<TEntity>(keyIndex, dateTimeValue);
|
||||
break;
|
||||
case OperationEnum.Greate:
|
||||
result = new ValueGreateSpecification<TEntity>(keyIndex, dateTimeValue);
|
||||
break;
|
||||
case OperationEnum.GreateOrEqual:
|
||||
result = new ValueGreateOrEqualSpecification<TEntity>(keyIndex, dateTimeValue);
|
||||
break;
|
||||
case OperationEnum.Less:
|
||||
result = new ValueLessSpecification<TEntity>(keyIndex, dateTimeValue);
|
||||
break;
|
||||
case OperationEnum.LessOrEqual:
|
||||
result = new ValueLessOrEqualSpecification<TEntity>(keyIndex, dateTimeValue);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
);
|
||||
|
||||
var result = node.AcceptVisitor(visitor);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Ardalis.Specification.EntityFrameworkCore" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
@ -7,7 +7,7 @@ namespace DD.Persistence.Database.Entity;
|
||||
|
||||
[Table("timestamped_values")]
|
||||
[PrimaryKey(nameof(DiscriminatorId), nameof(Timestamp))]
|
||||
public class TimestampedValues : ITimestampedItem
|
||||
public class TimestampedValues : ITimestampedItem, IValuesItem
|
||||
{
|
||||
[Comment("Временная отметка"), Key]
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
|
19
DD.Persistence.Database/EntityAbstractions/IValuesItem.cs
Normal file
19
DD.Persistence.Database/EntityAbstractions/IValuesItem.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using DD.Persistence.Database.Entity;
|
||||
|
||||
namespace DD.Persistence.Database.EntityAbstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Сущность с данными, принадлежащими к определенной схеме
|
||||
/// </summary>
|
||||
public interface IValuesItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Схема данных
|
||||
/// </summary>
|
||||
DataScheme? DataScheme { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Значения
|
||||
/// </summary>
|
||||
object[] Values { get; set; }
|
||||
}
|
14
DD.Persistence.Database/Specifications/EmptySpecification.cs
Normal file
14
DD.Persistence.Database/Specifications/EmptySpecification.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using Ardalis.Specification;
|
||||
|
||||
namespace DD.Persistence.Database.Specifications;
|
||||
|
||||
/// <summary>
|
||||
/// Пустая спецификация (вспомогательная)
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class EmptySpecification<T> : Specification<T>
|
||||
{
|
||||
public EmptySpecification()
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
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);
|
||||
}
|
||||
|
||||
public ValueEqaulSpecification(int index, DateTimeOffset? value)
|
||||
{
|
||||
// ToDo: рассмотреть возможность более вменяемого парсинга для даты
|
||||
Query.Where(e => DateTimeOffset.Parse(Convert.ToString(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, double? value)
|
||||
{
|
||||
Query.Where(e => Convert.ToDouble(e.Values[index]) >= value);
|
||||
}
|
||||
|
||||
public ValueGreateOrEqualSpecification(int index, DateTimeOffset? value)
|
||||
{
|
||||
Query.Where(e => DateTimeOffset.Parse(Convert.ToString(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, double? value)
|
||||
{
|
||||
Query.Where(e => Convert.ToDouble(e.Values[index]) > value);
|
||||
}
|
||||
|
||||
public ValueGreateSpecification(int index, DateTimeOffset? value)
|
||||
{
|
||||
Query.Where(e => DateTimeOffset.Parse(Convert.ToString(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, double? value)
|
||||
{
|
||||
Query.Where(e => Convert.ToDouble(e.Values[index]) <= value);
|
||||
}
|
||||
|
||||
public ValueLessOrEqualSpecification(int index, DateTimeOffset? value)
|
||||
{
|
||||
Query.Where(e => DateTimeOffset.Parse(Convert.ToString(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, double? value)
|
||||
{
|
||||
Query.Where(e => Convert.ToDouble(e.Values[index]) < value);
|
||||
}
|
||||
|
||||
public ValueLessSpecification(int index, DateTimeOffset? value)
|
||||
{
|
||||
Query.Where(e => DateTimeOffset.Parse(Convert.ToString(e.Values[index])!) < value);
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
using Ardalis.Specification;
|
||||
using DD.Persistence.Database.EntityAbstractions;
|
||||
|
||||
namespace DD.Persistence.Database.Specifications.ValuesItem;
|
||||
|
||||
/// <summary>
|
||||
/// Спецификация неравенства значений IValuesItem в соответствии с индексацией
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
public class ValueNotEqaulSpecification<TEntity> : Specification<TEntity>
|
||||
where TEntity : IValuesItem
|
||||
{
|
||||
public ValueNotEqaulSpecification(int index, string? value)
|
||||
{
|
||||
Query.Where(e => Convert.ToString(e.Values[index]) != value);
|
||||
}
|
||||
|
||||
public ValueNotEqaulSpecification(int index, double? value)
|
||||
{
|
||||
Query.Where(e => Convert.ToDouble(e.Values[index]) != value);
|
||||
}
|
||||
|
||||
public ValueNotEqaulSpecification(int index, DateTimeOffset? value)
|
||||
{
|
||||
Query.Where(e => DateTimeOffset.Parse(Convert.ToString(e.Values[index])!) != value);
|
||||
}
|
||||
}
|
@ -16,8 +16,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DD.Persistence.Database\DD.Persistence.Database.csproj" />
|
||||
<ProjectReference Include="..\DD.Persistence\DD.Persistence.csproj" />
|
||||
<ProjectReference Include="..\DD.Persistence.Database.Postgres\DD.Persistence.Database.Postgres.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
250
DD.Persistence.Test/FilterBuilderShould.cs
Normal file
250
DD.Persistence.Test/FilterBuilderShould.cs
Normal file
@ -0,0 +1,250 @@
|
||||
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;
|
||||
|
||||
namespace DD.Persistence.Test;
|
||||
public class FilterBuilderShould
|
||||
{
|
||||
private readonly SpecificationEvaluator SpecificationEvaluator;
|
||||
public FilterBuilderShould()
|
||||
{
|
||||
this.SpecificationEvaluator = new SpecificationEvaluator();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestFilterBuilding()
|
||||
{
|
||||
//arrange
|
||||
var discriminatorId = Guid.NewGuid();
|
||||
var dataScheme = new DataScheme()
|
||||
{
|
||||
DiscriminatorId = discriminatorId,
|
||||
PropNames = ["A", "B", "C"],
|
||||
PropTypes = [PropTypeEnum.DateTime, PropTypeEnum.Double, PropTypeEnum.String]
|
||||
};
|
||||
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
|
||||
DataScheme = dataScheme
|
||||
},
|
||||
new TimestampedValues {
|
||||
DiscriminatorId = discriminatorId,
|
||||
Timestamp = DateTimeOffset.Now.AddMinutes(-2),
|
||||
Values = new object[] { filterDate.AddMinutes(1), 2.21, "IsNotEqualText" }, // true
|
||||
DataScheme = dataScheme
|
||||
},
|
||||
new TimestampedValues {
|
||||
DiscriminatorId = discriminatorId,
|
||||
Timestamp = DateTimeOffset.Now.AddMinutes(-3),
|
||||
Values = new object[] { filterDate.AddMinutes(-1), 2.22, "IsNotEqualText" }, // false
|
||||
DataScheme = dataScheme
|
||||
},
|
||||
new TimestampedValues {
|
||||
DiscriminatorId = discriminatorId,
|
||||
Timestamp = DateTimeOffset.Now.AddMinutes(-4),
|
||||
Values = new object[] { filterDate.AddMinutes(-1), 2.21, "IsNotEqualText" }, // false
|
||||
DataScheme = dataScheme
|
||||
}
|
||||
}
|
||||
.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 dataScheme = new DataScheme()
|
||||
{
|
||||
DiscriminatorId = discriminatorId,
|
||||
PropNames = ["A"],
|
||||
PropTypes = [PropTypeEnum.Double]
|
||||
};
|
||||
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
|
||||
DataScheme = dataScheme
|
||||
},
|
||||
new TimestampedValues {
|
||||
DiscriminatorId = discriminatorId,
|
||||
Timestamp = DateTimeOffset.Now.AddMinutes(-2),
|
||||
Values = new object[] { 1.96 }, // false
|
||||
DataScheme = dataScheme
|
||||
},
|
||||
new TimestampedValues {
|
||||
DiscriminatorId = discriminatorId,
|
||||
Timestamp = DateTimeOffset.Now.AddMinutes(-3),
|
||||
Values = new object[] { 1.97 }, // true
|
||||
DataScheme = dataScheme
|
||||
},
|
||||
new TimestampedValues {
|
||||
DiscriminatorId = discriminatorId,
|
||||
Timestamp = DateTimeOffset.Now.AddMinutes(-4),
|
||||
Values = new object[] { 1.98 }, // false
|
||||
DataScheme = dataScheme
|
||||
},
|
||||
new TimestampedValues {
|
||||
DiscriminatorId = discriminatorId,
|
||||
Timestamp = DateTimeOffset.Now.AddMinutes(-5),
|
||||
Values = new object[] { 1.99 }, // true
|
||||
DataScheme = dataScheme
|
||||
},
|
||||
new TimestampedValues {
|
||||
DiscriminatorId = discriminatorId,
|
||||
Timestamp = DateTimeOffset.Now.AddMinutes(-6),
|
||||
Values = new object[] { 2 }, // false
|
||||
DataScheme = dataScheme
|
||||
}
|
||||
}
|
||||
.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 dataScheme = new DataScheme()
|
||||
{
|
||||
DiscriminatorId = discriminatorId,
|
||||
PropNames = ["A", "B", "C", "D"],
|
||||
PropTypes = [PropTypeEnum.Double, PropTypeEnum.Double, PropTypeEnum.String, PropTypeEnum.DateTime]
|
||||
};
|
||||
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
|
||||
DataScheme = dataScheme
|
||||
},
|
||||
new TimestampedValues {
|
||||
DiscriminatorId = discriminatorId,
|
||||
Timestamp = DateTimeOffset.Now.AddMinutes(-2),
|
||||
Values = new object[] { 2, 1.11, "IsNotEqualText", DateTimeOffset.Now.AddMinutes(-1) }, // true
|
||||
DataScheme = dataScheme
|
||||
},
|
||||
new TimestampedValues {
|
||||
DiscriminatorId = discriminatorId,
|
||||
Timestamp = DateTimeOffset.Now.AddMinutes(-3),
|
||||
Values = new object[] { 2, 2.22, "IsEqualText", DateTimeOffset.Now.AddMinutes(-1) }, // true
|
||||
DataScheme = dataScheme
|
||||
},
|
||||
new TimestampedValues {
|
||||
DiscriminatorId = discriminatorId,
|
||||
Timestamp = DateTimeOffset.Now.AddMinutes(-4),
|
||||
Values = new object[] { 2, 2.22, "IsNotEqualText", filterDate }, // true
|
||||
DataScheme = dataScheme
|
||||
},
|
||||
new TimestampedValues {
|
||||
DiscriminatorId = discriminatorId,
|
||||
Timestamp = DateTimeOffset.Now.AddMinutes(-1),
|
||||
Values = new object[] { 2, 2.22, "IsNotEqualText", DateTimeOffset.Now.AddMinutes(-1) }, // false
|
||||
DataScheme = dataScheme
|
||||
}
|
||||
}
|
||||
.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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user