From e548425b188b4ef242f837b8a53b21ae12e2376a Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Fri, 3 Feb 2023 16:00:45 +0500 Subject: [PATCH] Add PredicateBuilder to build complex predicate expressions --- AsbCloudInfrastructure/PredicateBuilder.cs | 80 +++++++++++++++++++ .../Repository/ProcessMapRepository.cs | 36 +++++---- 2 files changed, 99 insertions(+), 17 deletions(-) create mode 100644 AsbCloudInfrastructure/PredicateBuilder.cs diff --git a/AsbCloudInfrastructure/PredicateBuilder.cs b/AsbCloudInfrastructure/PredicateBuilder.cs new file mode 100644 index 00000000..ff0bf720 --- /dev/null +++ b/AsbCloudInfrastructure/PredicateBuilder.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace AsbCloudInfrastructure +{ +#nullable enable + /// + /// stolen from https://github.com/lotosbin/BinbinPredicateBuilder + /// + public static class PredicateBuilder + { + /// + /// Combines the first predicate with the second using the logical "and". + /// + public static Expression> And(this Expression> first, Expression> second) + { + return first.Compose(second, Expression.AndAlso); + } + + /// + /// Combines the first predicate with the second using the logical "or". + /// + public static Expression> Or(this Expression> first, Expression> second) + { + return first.Compose(second, Expression.OrElse); + } + + /// + /// Negates the predicate. + /// + public static Expression> Not(this Expression> expression) + { + var negated = Expression.Not(expression.Body); + return Expression.Lambda>(negated, expression.Parameters); + } + + private static Expression Compose(this Expression first, Expression second, Func merge) + { + var map = first.Parameters + .Select((f, i) => new { f, s = second.Parameters[i] }) + .ToDictionary(p => p.s, p => p.f); + + var tryReplaceParametr = (Expression node) => + { + if (node is ParameterExpression parameter) + if (map.TryGetValue(parameter, out ParameterExpression? replacement)) + return replacement; + return node; + }; + + var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body); + return Expression.Lambda(merge(first.Body, secondBody), first.Parameters); + } + + class ParameterRebinder : ExpressionVisitor + { + private readonly Dictionary map; + + private ParameterRebinder(Dictionary map) + { + this.map = map; + } + + public static Expression ReplaceParameters(Dictionary map, Expression exp) + { + return new ParameterRebinder(map).Visit(exp); + } + + protected override Expression VisitParameter(ParameterExpression parametr) + { + if (map.TryGetValue(parametr, out ParameterExpression? replacement)) + parametr = replacement; + return parametr; + } + } + } +} +#nullable disable \ No newline at end of file diff --git a/AsbCloudInfrastructure/Repository/ProcessMapRepository.cs b/AsbCloudInfrastructure/Repository/ProcessMapRepository.cs index 95330293..09f1f0a4 100644 --- a/AsbCloudInfrastructure/Repository/ProcessMapRepository.cs +++ b/AsbCloudInfrastructure/Repository/ProcessMapRepository.cs @@ -11,6 +11,7 @@ using Org.BouncyCastle.Asn1.Ocsp; using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; @@ -80,27 +81,28 @@ namespace AsbCloudInfrastructure.Repository private IQueryable BuildQuery(IEnumerable requests) { var query = GetQuery(); - Func? p = null; - foreach (var request in requests) + if (requests.Any()) { - var p2 = (ProcessMap map) => map.IdWell == request.IdWell; - - if (request.IdWellSectionType is not null) - p2 = (ProcessMap map) => p2(map) && map.IdWellSectionType == request.IdWellSectionType; - - if (request.UpdateFrom is not null) + Expression> predicate = map => false; + foreach (var request in requests) { - var timezone = wellService.GetTimezone(request.IdWell); - var updateFromUtc = request.UpdateFrom?.ToUtcDateTimeOffset(timezone.Hours); - p2 = (ProcessMap map) => p2(map) && map.LastUpdate >= updateFromUtc; + Expression> predicate2 = map => map.IdWell == request.IdWell; + + if (request.IdWellSectionType is not null) + predicate2 = predicate2.And(map => map.IdWellSectionType == request.IdWellSectionType); + + if (request.UpdateFrom is not null) + { + var timezone = wellService.GetTimezone(request.IdWell); + var updateFromUtc = request.UpdateFrom?.ToUtcDateTimeOffset(timezone.Hours); + predicate2 = predicate2.And(map => map.LastUpdate >= updateFromUtc); + } + + predicate = predicate.Or(predicate2); } - - p = p is null - ? p2 - : (ProcessMap map) => p(map) || p2(map); + query = query.Where(predicate); + } - if(p is not null) - query.Where(p); return query; } protected override ProcessMapDto Convert(ProcessMap entity)