< Summary

Information
Class: Pozitron.QuerySpecification.IncludeEvaluator
Assembly: Pozitron.QuerySpecification.EntityFrameworkCore
File(s): /home/runner/work/QuerySpecification/QuerySpecification/src/QuerySpecification.EntityFrameworkCore/Evaluators/IncludeEvaluator.cs
Tag: 52_11740816328
Line coverage
100%
Covered lines: 64
Uncovered lines: 0
Coverable lines: 64
Total lines: 122
Line coverage: 100%
Branch coverage
100%
Covered branches: 38
Total branches: 38
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%2222100%
get_EntityType()100%11100%
.ctor()100%11100%
Evaluate(...)100%1010100%
CreateIncludeDelegate(...)100%11100%
CreateThenIncludeDelegate(...)100%22100%
IsGenericEnumerable(...)100%44100%

File(s)

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

#LineLine coverage
 1using Microsoft.EntityFrameworkCore.Query;
 2using System.Collections;
 3using System.Collections.Concurrent;
 4using System.Diagnostics;
 5using System.Reflection;
 6
 7namespace Pozitron.QuerySpecification;
 8
 9/// <summary>
 10/// Evaluates a specification to include navigation properties.
 11/// </summary>
 12public sealed class IncludeEvaluator : IEvaluator
 13{
 114    private static readonly MethodInfo _includeMethodInfo = typeof(EntityFrameworkQueryableExtensions)
 115        .GetTypeInfo().GetDeclaredMethods(nameof(EntityFrameworkQueryableExtensions.Include))
 316        .Single(mi => mi.IsPublic && mi.GetGenericArguments().Length == 2
 317            && mi.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(IQueryable<>)
 318            && mi.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>));
 19
 120    private static readonly MethodInfo _thenIncludeAfterReferenceMethodInfo
 121        = typeof(EntityFrameworkQueryableExtensions)
 122            .GetTypeInfo().GetDeclaredMethods(nameof(EntityFrameworkQueryableExtensions.ThenInclude))
 323            .Single(mi => mi.IsPublic && mi.GetGenericArguments().Length == 3
 324                && mi.GetParameters()[0].ParameterType.GenericTypeArguments[1].IsGenericParameter
 325                && mi.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(IIncludableQueryable<,>)
 326                && mi.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>));
 27
 128    private static readonly MethodInfo _thenIncludeAfterEnumerableMethodInfo
 129        = typeof(EntityFrameworkQueryableExtensions)
 130            .GetTypeInfo().GetDeclaredMethods(nameof(EntityFrameworkQueryableExtensions.ThenInclude))
 331            .Single(mi => mi.IsPublic && mi.GetGenericArguments().Length == 3
 332                && !mi.GetParameters()[0].ParameterType.GenericTypeArguments[1].IsGenericParameter
 333                && mi.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof(IIncludableQueryable<,>)
 334                && mi.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>));
 35
 13136    private readonly record struct CacheKey(Type EntityType, Type PropertyType, Type? PreviousReturnType);
 137    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>
 143    public static IncludeEvaluator Instance = new();
 244    private IncludeEvaluator() { }
 45
 46    /// <inheritdoc/>
 47    public IQueryable<T> Evaluate<T>(IQueryable<T> source, Specification<T> specification) where T : class
 48    {
 5249        Type? previousReturnType = null;
 60850        foreach (var item in specification.Items)
 51        {
 25252            if (item.Type == ItemType.Include && item.Reference is LambdaExpression expr)
 53            {
 5954                if (item.Bag == (int)IncludeType.Include)
 55                {
 2756                    var key = new CacheKey(typeof(T), expr.ReturnType, null);
 2757                    previousReturnType = expr.ReturnType;
 2758                    var include = _cache.GetOrAdd(key, CreateIncludeDelegate);
 2759                    source = (IQueryable<T>)include(source, expr);
 60                }
 3261                else if (item.Bag == (int)IncludeType.ThenInclude)
 62                {
 3263                    var key = new CacheKey(typeof(T), expr.ReturnType, previousReturnType);
 3264                    previousReturnType = expr.ReturnType;
 3265                    var include = _cache.GetOrAdd(key, CreateThenIncludeDelegate);
 3266                    source = (IQueryable<T>)include(source, expr);
 67                }
 68            }
 69        }
 70
 5271        return source;
 72    }
 73
 74    private static Func<IQueryable, LambdaExpression, IQueryable> CreateIncludeDelegate(CacheKey cacheKey)
 75    {
 776        var includeMethod = _includeMethodInfo.MakeGenericMethod(cacheKey.EntityType, cacheKey.PropertyType);
 777        var sourceParameter = Expression.Parameter(typeof(IQueryable));
 778        var selectorParameter = Expression.Parameter(typeof(LambdaExpression));
 79
 780        var call = Expression.Call(
 781              includeMethod,
 782              Expression.Convert(sourceParameter, typeof(IQueryable<>).MakeGenericType(cacheKey.EntityType)),
 783              Expression.Convert(selectorParameter, typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType
 84
 785        var lambda = Expression.Lambda<Func<IQueryable, LambdaExpression, IQueryable>>(call, sourceParameter, selectorPa
 786        return lambda.Compile();
 87    }
 88
 89    private static Func<IQueryable, LambdaExpression, IQueryable> CreateThenIncludeDelegate(CacheKey cacheKey)
 90    {
 91        Debug.Assert(cacheKey.PreviousReturnType is not null);
 92
 1693        var thenIncludeInfo = IsGenericEnumerable(cacheKey.PreviousReturnType, out var previousPropertyType)
 1694            ? _thenIncludeAfterEnumerableMethodInfo
 1695            : _thenIncludeAfterReferenceMethodInfo;
 96
 1697        var thenIncludeMethod = thenIncludeInfo.MakeGenericMethod(cacheKey.EntityType, previousPropertyType, cacheKey.Pr
 1698        var sourceParameter = Expression.Parameter(typeof(IQueryable));
 1699        var selectorParameter = Expression.Parameter(typeof(LambdaExpression));
 100
 16101        var call = Expression.Call(
 16102                thenIncludeMethod,
 16103                // We must pass cacheKey.PreviousReturnType. It must be exact type, not the generic type argument
 16104                Expression.Convert(sourceParameter, typeof(IIncludableQueryable<,>).MakeGenericType(cacheKey.EntityType,
 16105                Expression.Convert(selectorParameter, typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericTy
 106
 16107        var lambda = Expression.Lambda<Func<IQueryable, LambdaExpression, IQueryable>>(call, sourceParameter, selectorPa
 16108        return lambda.Compile();
 109    }
 110
 111    private static bool IsGenericEnumerable(Type type, out Type propertyType)
 112    {
 16113        if (type.IsGenericType && typeof(IEnumerable).IsAssignableFrom(type))
 114        {
 9115            propertyType = type.GenericTypeArguments[0];
 9116            return true;
 117        }
 118
 7119        propertyType = type;
 7120        return false;
 121    }
 122}