Правки по ревью #4

This commit is contained in:
Roman Efremov 2025-02-06 12:32:28 +05:00
parent 44bc335151
commit bcb9749b1a
19 changed files with 311 additions and 320 deletions

View File

@ -1,83 +0,0 @@
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;
}

View File

@ -1,137 +0,0 @@
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;
}
}

View File

@ -7,6 +7,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Ardalis.Specification.EntityFrameworkCore" Version="8.0.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">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>

View File

@ -7,11 +7,6 @@ namespace DD.Persistence.Database.EntityAbstractions;
/// </summary> /// </summary>
public interface IValuesItem public interface IValuesItem
{ {
/// <summary>
/// Схема данных
/// </summary>
DataScheme? DataScheme { get; set; }
/// <summary> /// <summary>
/// Значения /// Значения
/// </summary> /// </summary>

View File

@ -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;
}
}

View 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) }
};
}

View 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;
}

View File

@ -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,

View 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);
}
}
}

View File

@ -1,14 +0,0 @@
using Ardalis.Specification;
namespace DD.Persistence.Database.Specifications;
/// <summary>
/// Пустая спецификация (вспомогательная)
/// </summary>
/// <typeparam name="T"></typeparam>
public class EmptySpecification<T> : Specification<T>
{
public EmptySpecification()
{
}
}

View 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);
}
}

View File

@ -19,10 +19,4 @@ public class ValueEqaulSpecification<TEntity> : Specification<TEntity>
{ {
Query.Where(e => Convert.ToDouble(e.Values[index]) == 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);
}
} }

View File

@ -10,13 +10,13 @@ namespace DD.Persistence.Database.Specifications.ValuesItem;
public class ValueGreateOrEqualSpecification<TEntity> : Specification<TEntity> public class ValueGreateOrEqualSpecification<TEntity> : Specification<TEntity>
where TEntity : IValuesItem 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) public ValueGreateOrEqualSpecification(int index, double? value)
{ {
Query.Where(e => Convert.ToDouble(e.Values[index]) >= 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);
}
} }

View File

@ -10,13 +10,13 @@ namespace DD.Persistence.Database.Specifications.ValuesItem;
public class ValueGreateSpecification<TEntity> : Specification<TEntity> public class ValueGreateSpecification<TEntity> : Specification<TEntity>
where TEntity : IValuesItem 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) public ValueGreateSpecification(int index, double? value)
{ {
Query.Where(e => Convert.ToDouble(e.Values[index]) > 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);
}
} }

View File

@ -10,13 +10,13 @@ namespace DD.Persistence.Database.Specifications.ValuesItem;
public class ValueLessOrEqualSpecification<TEntity> : Specification<TEntity> public class ValueLessOrEqualSpecification<TEntity> : Specification<TEntity>
where TEntity : IValuesItem 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) public ValueLessOrEqualSpecification(int index, double? value)
{ {
Query.Where(e => Convert.ToDouble(e.Values[index]) <= 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);
}
} }

View File

@ -10,13 +10,13 @@ namespace DD.Persistence.Database.Specifications.ValuesItem;
public class ValueLessSpecification<TEntity> : Specification<TEntity> public class ValueLessSpecification<TEntity> : Specification<TEntity>
where TEntity : IValuesItem 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) public ValueLessSpecification(int index, double? value)
{ {
Query.Where(e => Convert.ToDouble(e.Values[index]) < 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);
}
} }

View File

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

View File

@ -4,8 +4,11 @@ using DD.Persistence.Filter.Models;
using DD.Persistence.Filter.Models.Enumerations; using DD.Persistence.Filter.Models.Enumerations;
using DD.Persistence.Models; using DD.Persistence.Models;
using DD.Persistence.Database.Postgres.Helpers; using DD.Persistence.Database.Postgres.Helpers;
using System.Text.Json;
namespace DD.Persistence.Test; namespace DD.Persistence.Test;
/// ToDo: переписать под Theory
public class FilterBuilderShould public class FilterBuilderShould
{ {
private readonly SpecificationEvaluator SpecificationEvaluator; private readonly SpecificationEvaluator SpecificationEvaluator;
@ -19,12 +22,28 @@ public class FilterBuilderShould
{ {
//arrange //arrange
var discriminatorId = Guid.NewGuid(); var discriminatorId = Guid.NewGuid();
var dataScheme = new DataScheme() var dataSchemeProperties = new SchemePropertyDto[]
{ {
DiscriminatorId = discriminatorId, new SchemePropertyDto()
PropNames = ["A", "B", "C"], {
PropTypes = [PropTypeEnum.DateTime, PropTypeEnum.Double, PropTypeEnum.String] 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 filterDate = DateTime.Now.AddMinutes(-1);
var root = new TVertex( var root = new TVertex(
OperationEnum.Or, OperationEnum.Or,
@ -40,26 +59,22 @@ public class FilterBuilderShould
new TimestampedValues { new TimestampedValues {
DiscriminatorId = discriminatorId, DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-1), Timestamp = DateTimeOffset.Now.AddMinutes(-1),
Values = new object[] { filterDate.AddMinutes(-1), 200, "IsEqualText" }, // true Values = new object[] { filterDate.AddMinutes(-1), 200, "IsEqualText" } // true
DataScheme = dataScheme
}, },
new TimestampedValues { new TimestampedValues {
DiscriminatorId = discriminatorId, DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-2), Timestamp = DateTimeOffset.Now.AddMinutes(-2),
Values = new object[] { filterDate.AddMinutes(1), 2.21, "IsNotEqualText" }, // true Values = new object[] { filterDate.AddMinutes(1), 2.21, "IsNotEqualText" } // true
DataScheme = dataScheme
}, },
new TimestampedValues { new TimestampedValues {
DiscriminatorId = discriminatorId, DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-3), Timestamp = DateTimeOffset.Now.AddMinutes(-3),
Values = new object[] { filterDate.AddMinutes(-1), 2.22, "IsNotEqualText" }, // false Values = new object[] { filterDate.AddMinutes(-1), 2.22, "IsNotEqualText" } // false
DataScheme = dataScheme
}, },
new TimestampedValues { new TimestampedValues {
DiscriminatorId = discriminatorId, DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-4), Timestamp = DateTimeOffset.Now.AddMinutes(-4),
Values = new object[] { filterDate.AddMinutes(-1), 2.21, "IsNotEqualText" }, // false Values = new object[] { filterDate.AddMinutes(-1), 2.21, "IsNotEqualText" } // false
DataScheme = dataScheme
} }
} }
.AsQueryable(); .AsQueryable();
@ -86,12 +101,16 @@ public class FilterBuilderShould
{ {
//arrange //arrange
var discriminatorId = Guid.NewGuid(); var discriminatorId = Guid.NewGuid();
var dataScheme = new DataScheme() var dataSchemeProperties = new SchemePropertyDto[]
{ {
DiscriminatorId = discriminatorId, new SchemePropertyDto()
PropNames = ["A"], {
PropTypes = [PropTypeEnum.Double] Index = 0,
PropertyName = "A",
PropertyKind = JsonValueKind.Number
}
}; };
var dataScheme = new DataSchemeDto(discriminatorId, dataSchemeProperties);
var root = new TVertex( var root = new TVertex(
OperationEnum.Or, OperationEnum.Or,
new TVertex( new TVertex(
@ -118,38 +137,32 @@ public class FilterBuilderShould
new TimestampedValues { new TimestampedValues {
DiscriminatorId = discriminatorId, DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-1), Timestamp = DateTimeOffset.Now.AddMinutes(-1),
Values = new object[] { 1 }, // true Values = new object[] { 1 } // true
DataScheme = dataScheme
}, },
new TimestampedValues { new TimestampedValues {
DiscriminatorId = discriminatorId, DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-2), Timestamp = DateTimeOffset.Now.AddMinutes(-2),
Values = new object[] { 1.96 }, // false Values = new object[] { 1.96 } // false
DataScheme = dataScheme
}, },
new TimestampedValues { new TimestampedValues {
DiscriminatorId = discriminatorId, DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-3), Timestamp = DateTimeOffset.Now.AddMinutes(-3),
Values = new object[] { 1.97 }, // true Values = new object[] { 1.97 } // true
DataScheme = dataScheme
}, },
new TimestampedValues { new TimestampedValues {
DiscriminatorId = discriminatorId, DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-4), Timestamp = DateTimeOffset.Now.AddMinutes(-4),
Values = new object[] { 1.98 }, // false Values = new object[] { 1.98 } // false
DataScheme = dataScheme
}, },
new TimestampedValues { new TimestampedValues {
DiscriminatorId = discriminatorId, DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-5), Timestamp = DateTimeOffset.Now.AddMinutes(-5),
Values = new object[] { 1.99 }, // true Values = new object[] { 1.99 } // true
DataScheme = dataScheme
}, },
new TimestampedValues { new TimestampedValues {
DiscriminatorId = discriminatorId, DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-6), Timestamp = DateTimeOffset.Now.AddMinutes(-6),
Values = new object[] { 2 }, // false Values = new object[] { 2 } // false
DataScheme = dataScheme
} }
} }
.AsQueryable(); .AsQueryable();
@ -177,12 +190,35 @@ public class FilterBuilderShould
//arrange //arrange
var discriminatorId = Guid.NewGuid(); var discriminatorId = Guid.NewGuid();
var filterDate = DateTimeOffset.Now; var filterDate = DateTimeOffset.Now;
var dataScheme = new DataScheme() var dataSchemeProperties = new SchemePropertyDto[]
{ {
DiscriminatorId = discriminatorId, new SchemePropertyDto()
PropNames = ["A", "B", "C", "D"], {
PropTypes = [PropTypeEnum.Double, PropTypeEnum.Double, PropTypeEnum.String, PropTypeEnum.DateTime] 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( var root = new TVertex(
OperationEnum.Or, OperationEnum.Or,
new TVertex( new TVertex(
@ -201,32 +237,27 @@ public class FilterBuilderShould
new TimestampedValues { new TimestampedValues {
DiscriminatorId = discriminatorId, DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-1), Timestamp = DateTimeOffset.Now.AddMinutes(-1),
Values = new object[] { 1, 2.22, "IsNotEqualText", DateTimeOffset.Now.AddMinutes(-1) }, // true Values = new object[] { 1, 2.22, "IsNotEqualText", DateTimeOffset.Now.AddMinutes(-1) } // true
DataScheme = dataScheme
}, },
new TimestampedValues { new TimestampedValues {
DiscriminatorId = discriminatorId, DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-2), Timestamp = DateTimeOffset.Now.AddMinutes(-2),
Values = new object[] { 2, 1.11, "IsNotEqualText", DateTimeOffset.Now.AddMinutes(-1) }, // true Values = new object[] { 2, 1.11, "IsNotEqualText", DateTimeOffset.Now.AddMinutes(-1) } // true
DataScheme = dataScheme
}, },
new TimestampedValues { new TimestampedValues {
DiscriminatorId = discriminatorId, DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-3), Timestamp = DateTimeOffset.Now.AddMinutes(-3),
Values = new object[] { 2, 2.22, "IsEqualText", DateTimeOffset.Now.AddMinutes(-1) }, // true Values = new object[] { 2, 2.22, "IsEqualText", DateTimeOffset.Now.AddMinutes(-1) } // true
DataScheme = dataScheme
}, },
new TimestampedValues { new TimestampedValues {
DiscriminatorId = discriminatorId, DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-4), Timestamp = DateTimeOffset.Now.AddMinutes(-4),
Values = new object[] { 2, 2.22, "IsNotEqualText", filterDate }, // true Values = new object[] { 2, 2.22, "IsNotEqualText", filterDate } // true
DataScheme = dataScheme
}, },
new TimestampedValues { new TimestampedValues {
DiscriminatorId = discriminatorId, DiscriminatorId = discriminatorId,
Timestamp = DateTimeOffset.Now.AddMinutes(-1), Timestamp = DateTimeOffset.Now.AddMinutes(-1),
Values = new object[] { 2, 2.22, "IsNotEqualText", DateTimeOffset.Now.AddMinutes(-1) }, // false Values = new object[] { 2, 2.22, "IsNotEqualText", DateTimeOffset.Now.AddMinutes(-1) } // false
DataScheme = dataScheme
} }
} }
.AsQueryable(); .AsQueryable();

View File

@ -2,6 +2,7 @@ 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
@ -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;
}
} }