|  |  | 1 |  | using System.Data; | 
|  |  | 2 |  | using System.Diagnostics; | 
|  |  | 3 |  | using System.Reflection; | 
|  |  | 4 |  |  | 
|  |  | 5 |  | namespace Pozitron.QuerySpecification; | 
|  |  | 6 |  |  | 
|  |  | 7 |  | internal 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 | 
|  | 64 | 10 |  |     private record StringVar(string Format); | 
|  | 1 | 11 |  |     private static readonly PropertyInfo _stringFormatProperty = typeof(StringVar).GetProperty(nameof(StringVar.Format)) | 
|  | 1 | 12 |  |     private static readonly MemberExpression _functions = Expression.Property(null, typeof(EF).GetProperty(nameof(EF.Fun | 
|  | 1 | 13 |  |     private static readonly MethodInfo _likeMethodInfo = typeof(DbFunctionsExtensions) | 
|  | 1 | 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) | 
|  | 32 | 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 |  |  | 
|  | 23 | 27 |  |         Expression? combinedExpr = null; | 
|  | 23 | 28 |  |         ParameterExpression? mainParam = null; | 
|  | 23 | 29 |  |         ParameterReplacerVisitor? visitor = null; | 
|  |  | 30 |  |  | 
|  | 110 | 31 |  |         foreach (var item in likeItems) | 
|  |  | 32 |  |         { | 
|  | 32 | 33 |  |             if (item.Reference is not SpecLike<T> specLike) continue; | 
|  |  | 34 |  |  | 
|  | 32 | 35 |  |             mainParam ??= specLike.KeySelector.Parameters[0]; | 
|  |  | 36 |  |  | 
|  | 32 | 37 |  |             var selectorExpr = specLike.KeySelector.Body; | 
|  | 32 | 38 |  |             if (mainParam != specLike.KeySelector.Parameters[0]) | 
|  |  | 39 |  |             { | 
|  | 10 | 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. | 
|  | 10 | 44 |  |                 visitor.Update(specLike.KeySelector.Parameters[0], mainParam); | 
|  | 10 | 45 |  |                 selectorExpr = visitor.Visit(selectorExpr); | 
|  |  | 46 |  |             } | 
|  |  | 47 |  |  | 
|  | 32 | 48 |  |             var patternExpr = StringAsExpression(specLike.Pattern); | 
|  |  | 49 |  |  | 
|  | 32 | 50 |  |             var likeExpr = Expression.Call( | 
|  | 32 | 51 |  |                 null, | 
|  | 32 | 52 |  |                 _likeMethodInfo, | 
|  | 32 | 53 |  |                 _functions, | 
|  | 32 | 54 |  |                 selectorExpr, | 
|  | 32 | 55 |  |                 patternExpr); | 
|  |  | 56 |  |  | 
|  | 32 | 57 |  |             combinedExpr = combinedExpr is null | 
|  | 32 | 58 |  |                 ? likeExpr | 
|  | 32 | 59 |  |                 : Expression.OrElse(combinedExpr, likeExpr); | 
|  |  | 60 |  |         } | 
|  |  | 61 |  |  | 
|  | 23 | 62 |  |         return combinedExpr is null || mainParam is null | 
|  | 23 | 63 |  |             ? source | 
|  | 23 | 64 |  |             : source.Where(Expression.Lambda<Func<T, bool>>(combinedExpr, mainParam)); | 
|  |  | 65 |  |     } | 
|  |  | 66 |  | } | 
|  |  | 67 |  |  | 
|  |  | 68 |  | internal sealed class ParameterReplacerVisitor : ExpressionVisitor | 
|  |  | 69 |  | { | 
|  |  | 70 |  |     private ParameterExpression _oldParameter; | 
|  |  | 71 |  |     private Expression _newExpression; | 
|  |  | 72 |  |  | 
|  |  | 73 |  |     internal ParameterReplacerVisitor(ParameterExpression oldParameter, Expression newExpression) => | 
|  |  | 74 |  |         (_oldParameter, _newExpression) = (oldParameter, newExpression); | 
|  |  | 75 |  |  | 
|  |  | 76 |  |     internal void Update(ParameterExpression oldParameter, Expression newExpression) => | 
|  |  | 77 |  |         (_oldParameter, _newExpression) = (oldParameter, newExpression); | 
|  |  | 78 |  |  | 
|  |  | 79 |  |     protected override Expression VisitParameter(ParameterExpression node) => | 
|  |  | 80 |  |         node == _oldParameter ? _newExpression : node; | 
|  |  | 81 |  | } |