< Summary

Information
Class: Pozitron.QuerySpecification.IncludeEvaluator
Assembly: Pozitron.QuerySpecification.EntityFrameworkCore
File(s): /home/runner/work/QuerySpecification/QuerySpecification/src/QuerySpecification.EntityFrameworkCore/Evaluators/IncludeEvaluator.cs
Tag: 67_15587897385
Line coverage
100%
Covered lines: 60
Uncovered lines: 0
Coverable lines: 60
Total lines: 111
Line coverage: 100%
Branch coverage
100%
Covered branches: 36
Total branches: 36
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%2222100%
.ctor()100%11100%
Evaluate(...)100%1212100%
CreateIncludeDelegate(...)100%11100%
CreateThenIncludeDelegate(...)100%22100%

File(s)

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

#LineLine coverage
 1using Microsoft.EntityFrameworkCore.Query;
 2using System.Collections.Concurrent;
 3using System.Diagnostics;
 4using System.Reflection;
 5
 6namespace Pozitron.QuerySpecification;
 7
 8/// <summary>
 9/// Evaluates a specification to include navigation properties.
 10/// </summary>
 11[EvaluatorDiscovery(Order = 40)]
 12public sealed class IncludeEvaluator : IEvaluator
 13{
 214    private static readonly MethodInfo _includeMethodInfo = typeof(EntityFrameworkQueryableExtensions)
 215        .GetTypeInfo().GetDeclaredMethods(nameof(EntityFrameworkQueryableExtensions.Include))
 616        .Single(mi => mi.IsPublic && mi.GetGenericArguments().Length == 2
 617            && mi.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(IQueryable<>)
 618            && mi.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>));
 19
 220    private static readonly MethodInfo _thenIncludeAfterReferenceMethodInfo
 221        = typeof(EntityFrameworkQueryableExtensions)
 222            .GetTypeInfo().GetDeclaredMethods(nameof(EntityFrameworkQueryableExtensions.ThenInclude))
 623            .Single(mi => mi.IsPublic && mi.GetGenericArguments().Length == 3
 624                && mi.GetParameters()[0].ParameterType.GenericTypeArguments[1].IsGenericParameter
 625                && mi.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(IIncludableQueryable<,>)
 626                && mi.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>));
 27
 228    private static readonly MethodInfo _thenIncludeAfterEnumerableMethodInfo
 229        = typeof(EntityFrameworkQueryableExtensions)
 230            .GetTypeInfo().GetDeclaredMethods(nameof(EntityFrameworkQueryableExtensions.ThenInclude))
 631            .Single(mi => mi.IsPublic && mi.GetGenericArguments().Length == 3
 632                && !mi.GetParameters()[0].ParameterType.GenericTypeArguments[1].IsGenericParameter
 633                && mi.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(IIncludableQueryable<,>)
 634                && mi.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>));
 35
 16636    private readonly record struct CacheKey(int IncludeType, Type EntityType, Type PropertyType, Type? PreviousReturnTyp
 237    private static readonly ConcurrentDictionary<CacheKey, Func<IQueryable, LambdaExpression, IQueryable>> _cache = new(
 38
 39
 40    /// <summary>
 41    /// Gets the singleton instance of the <see cref="IncludeEvaluator"/> class.
 42    /// </summary>
 243    public static IncludeEvaluator Instance = new();
 444    private IncludeEvaluator() { }
 45
 46    /// <inheritdoc/>
 47    public IQueryable<T> Evaluate<T>(IQueryable<T> source, Specification<T> specification) where T : class
 48    {
 5449        Type? previousReturnType = null;
 64450        foreach (var item in specification.Items)
 51        {
 26852            if (item.Type == ItemType.Include && item.Reference is LambdaExpression expr)
 53            {
 6354                if (item.Bag == (int)IncludeType.Include)
 55                {
 2956                    var key = new CacheKey(item.Bag, typeof(T), expr.ReturnType, null);
 2957                    previousReturnType = expr.ReturnType;
 2958                    var include = _cache.GetOrAdd(key, CreateIncludeDelegate);
 2959                    source = (IQueryable<T>)include(source, expr);
 60                }
 3461                else if (item.Bag == (int)IncludeType.ThenIncludeAfterReference
 3462                      || item.Bag == (int)IncludeType.ThenIncludeAfterCollection)
 63                {
 3464                    var key = new CacheKey(item.Bag, typeof(T), expr.ReturnType, previousReturnType);
 3465                    previousReturnType = expr.ReturnType;
 3466                    var include = _cache.GetOrAdd(key, CreateThenIncludeDelegate);
 3467                    source = (IQueryable<T>)include(source, expr);
 68                }
 69            }
 70        }
 71
 5472        return source;
 73    }
 74
 75    private static Func<IQueryable, LambdaExpression, IQueryable> CreateIncludeDelegate(CacheKey cacheKey)
 76    {
 877        var includeMethod = _includeMethodInfo.MakeGenericMethod(cacheKey.EntityType, cacheKey.PropertyType);
 878        var sourceParameter = Expression.Parameter(typeof(IQueryable));
 879        var selectorParameter = Expression.Parameter(typeof(LambdaExpression));
 80
 881        var call = Expression.Call(
 882              includeMethod,
 883              Expression.Convert(sourceParameter, typeof(IQueryable<>).MakeGenericType(cacheKey.EntityType)),
 884              Expression.Convert(selectorParameter, typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType
 85
 886        var lambda = Expression.Lambda<Func<IQueryable, LambdaExpression, IQueryable>>(call, sourceParameter, selectorPa
 887        return lambda.Compile();
 88    }
 89
 90    private static Func<IQueryable, LambdaExpression, IQueryable> CreateThenIncludeDelegate(CacheKey cacheKey)
 91    {
 92        Debug.Assert(cacheKey.PreviousReturnType is not null);
 93
 1894        var (thenIncludeMethod, previousPropertyType) = cacheKey.IncludeType == (int)IncludeType.ThenIncludeAfterReferen
 1895            ? (_thenIncludeAfterReferenceMethodInfo, cacheKey.PreviousReturnType)
 1896            : (_thenIncludeAfterEnumerableMethodInfo, cacheKey.PreviousReturnType.GenericTypeArguments[0]);
 97
 1898        var thenIncludeMethodGeneric = thenIncludeMethod.MakeGenericMethod(cacheKey.EntityType, previousPropertyType, ca
 1899        var sourceParameter = Expression.Parameter(typeof(IQueryable));
 18100        var selectorParameter = Expression.Parameter(typeof(LambdaExpression));
 101
 18102        var call = Expression.Call(
 18103                thenIncludeMethodGeneric,
 18104                // We must pass cacheKey.PreviousReturnType. It must be exact type, not the generic type argument
 18105                Expression.Convert(sourceParameter, typeof(IIncludableQueryable<,>).MakeGenericType(cacheKey.EntityType,
 18106                Expression.Convert(selectorParameter, typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericTy
 107
 18108        var lambda = Expression.Lambda<Func<IQueryable, LambdaExpression, IQueryable>>(call, sourceParameter, selectorPa
 18109        return lambda.Compile();
 110    }
 111}