< Summary

Information
Class: Pozitron.QuerySpecification.ParameterReplacerVisitor
Assembly: Pozitron.QuerySpecification.EntityFrameworkCore
File(s): /home/runner/work/QuerySpecification/QuerySpecification/src/QuerySpecification.EntityFrameworkCore/Evaluators/LikeExtension.cs
Tag: 74_17335419374
Line coverage
100%
Covered lines: 4
Uncovered lines: 0
Coverable lines: 4
Total lines: 81
Line coverage: 100%
Branch coverage
100%
Covered branches: 2
Total branches: 2
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%11100%
Update(...)100%11100%
VisitParameter(...)100%22100%

File(s)

/home/runner/work/QuerySpecification/QuerySpecification/src/QuerySpecification.EntityFrameworkCore/Evaluators/LikeExtension.cs

#LineLine coverage
 1using System.Data;
 2using System.Diagnostics;
 3using System.Reflection;
 4
 5namespace Pozitron.QuerySpecification;
 6
 7internal static class LikeExtension
 8{
 9    // We'll name the property Format just so we match the produced SQL query parameter name (in case of interpolated st
 10    private record StringVar(string Format);
 11    private static readonly PropertyInfo _stringFormatProperty = typeof(StringVar).GetProperty(nameof(StringVar.Format))
 12    private static readonly MemberExpression _functions = Expression.Property(null, typeof(EF).GetProperty(nameof(EF.Fun
 13    private static readonly MethodInfo _likeMethodInfo = typeof(DbFunctionsExtensions)
 14        .GetMethod(nameof(DbFunctionsExtensions.Like), [typeof(DbFunctions), typeof(string), typeof(string)])!;
 15
 16
 17    // It's required so EF can generate parameterized query.
 18    // In the past I've been creating closures for this, e.g. var patternAsExpression = ((Expression<Func<string>>)(() =
 19    // But, that allocates 168 bytes. So, this is more efficient way.
 20    private static MemberExpression StringAsExpression(string value)
 21        => Expression.Property(Expression.Constant(new StringVar(value)), _stringFormatProperty);
 22
 23    public static IQueryable<T> ApplyLikesAsOrGroup<T>(this IQueryable<T> source, ReadOnlySpan<SpecItem> likeItems)
 24    {
 25        Debug.Assert(_likeMethodInfo is not null);
 26
 27        Expression? combinedExpr = null;
 28        ParameterExpression? mainParam = null;
 29        ParameterReplacerVisitor? visitor = null;
 30
 31        foreach (var item in likeItems)
 32        {
 33            if (item.Reference is not SpecLike<T> specLike) continue;
 34
 35            mainParam ??= specLike.KeySelector.Parameters[0];
 36
 37            var selectorExpr = specLike.KeySelector.Body;
 38            if (mainParam != specLike.KeySelector.Parameters[0])
 39            {
 40                visitor ??= new ParameterReplacerVisitor(specLike.KeySelector.Parameters[0], mainParam);
 41
 42                // If there are more than 2 likes, we want to avoid creating a new visitor instance (saving 32 bytes per
 43                // We're in a sequential loop, no concurrency issues.
 44                visitor.Update(specLike.KeySelector.Parameters[0], mainParam);
 45                selectorExpr = visitor.Visit(selectorExpr);
 46            }
 47
 48            var patternExpr = StringAsExpression(specLike.Pattern);
 49
 50            var likeExpr = Expression.Call(
 51                null,
 52                _likeMethodInfo,
 53                _functions,
 54                selectorExpr,
 55                patternExpr);
 56
 57            combinedExpr = combinedExpr is null
 58                ? likeExpr
 59                : Expression.OrElse(combinedExpr, likeExpr);
 60        }
 61
 62        return combinedExpr is null || mainParam is null
 63            ? source
 64            : source.Where(Expression.Lambda<Func<T, bool>>(combinedExpr, mainParam));
 65    }
 66}
 67
 68internal sealed class ParameterReplacerVisitor : ExpressionVisitor
 69{
 70    private ParameterExpression _oldParameter;
 71    private Expression _newExpression;
 72
 1173    internal ParameterReplacerVisitor(ParameterExpression oldParameter, Expression newExpression) =>
 1174        (_oldParameter, _newExpression) = (oldParameter, newExpression);
 75
 76    internal void Update(ParameterExpression oldParameter, Expression newExpression) =>
 1077        (_oldParameter, _newExpression) = (oldParameter, newExpression);
 78
 79    protected override Expression VisitParameter(ParameterExpression node) =>
 1380        node == _oldParameter ? _newExpression : node;
 81}