< Summary

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