< Summary - Code Coverage

Information
Class: Plainquire.Sort.QueryableExtensions
Assembly: Plainquire.Sort
File(s): /home/runner/work/plainquire/plainquire/Plainquire.Sort/Plainquire.Sort/Extensions/QueryableExtensions.cs
Tag: 64_13932151703
Line coverage
94%
Covered lines: 72
Uncovered lines: 4
Coverable lines: 76
Total lines: 186
Line coverage: 94.7%
Branch coverage
91%
Covered branches: 44
Total branches: 48
Branch coverage: 91.6%
Method coverage
100%
Covered methods: 9
Total methods: 9
Method coverage: 100%

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
OrderBy(...)100%11100%
OrderBy(...)96.15%262695.45%
OrderBy(...)100%11100%
OrderByDescending(...)100%11100%
ThenBy(...)100%11100%
ThenByDescending(...)100%11100%
OrderBy(...)83.33%6696%
ConditionalAccessRequested(...)83.33%6685.71%
Add(...)90%101094.11%

File(s)

/home/runner/work/plainquire/plainquire/Plainquire.Sort/Plainquire.Sort/Extensions/QueryableExtensions.cs

#LineLine coverage
 1using Plainquire.Filter.Abstractions;
 2using Plainquire.Filter.Abstractions.Exceptions;
 3using Plainquire.Sort.Abstractions;
 4using System;
 5using System.Collections.Generic;
 6using System.Diagnostics.CodeAnalysis;
 7using System.Linq;
 8using System.Linq.Expressions;
 9using System.Reflection;
 10
 11namespace Plainquire.Sort;
 12
 13/// <summary>
 14/// Extension methods for <see cref="IQueryable{TEntity}"/>
 15/// </summary>
 16[SuppressMessage("ReSharper", "MemberCanBePrivate.Global", Justification = "Provided as library, can be used from outsid
 17public static class QueryableExtensions
 18{
 19    /// <inheritdoc cref="OrderBy{TEntity}(IQueryable{TEntity}, EntitySort{TEntity}, ISortInterceptor?)"/>
 20    public static IOrderedQueryable<TEntity> OrderBy<TEntity>(this IEnumerable<TEntity> source, EntitySort<TEntity> sort
 16221        => source.AsQueryable().OrderBy(sort, interceptor);
 22
 23    /// <summary>
 24    /// Sorts the elements of a sequence according to the given <paramref name="sort"/>.
 25    /// </summary>
 26    /// <typeparam name="TEntity"></typeparam>
 27    /// <param name="source">The elements to sort.</param>
 28    /// <param name="sort">The <see cref="EntitySort{TEntity}"/> used to sort the elements.</param>
 29    /// <param name="interceptor">An interceptor to manipulate the generated sort order.</param>
 30    public static IOrderedQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, EntitySort<TEntity> sort,
 31    {
 31732        var propertySorts = sort.PropertySorts.OrderBy(x => x.Position).ToList();
 31733        if (!propertySorts.Any())
 034            return (IOrderedQueryable<TEntity>)source.Provider.CreateQuery<TEntity>(source.Expression);
 35
 31736        var configuration = sort.Configuration ?? SortConfiguration.Default ?? new SortConfiguration();
 31737        interceptor ??= ISortInterceptor.Default;
 38
 31739        var first = propertySorts[0];
 31740        var result = interceptor?.OrderBy(source, first);
 31741        if (result == null)
 42        {
 31143            var ascending = first.Direction == SortDirection.Ascending;
 31144            result ??= ascending
 31145                ? source.OrderBy(first.PropertyPath, configuration)
 31146                : source.OrderByDescending(first.PropertyPath, configuration);
 47        }
 48
 69249        foreach (var sortedProperty in propertySorts.Skip(1))
 50        {
 3651            var interceptedResult = interceptor?.ThenBy(result, sortedProperty);
 3652            if (interceptedResult != null)
 53            {
 654                result = interceptedResult;
 655                continue;
 56            }
 57
 3058            var ascending = sortedProperty.Direction == SortDirection.Ascending;
 3059            result = ascending
 3060                ? result.ThenBy(sortedProperty.PropertyPath, configuration)
 3061                : result.ThenByDescending(sortedProperty.PropertyPath, configuration);
 62        }
 63
 31064        return result;
 65    }
 66
 67    /// <summary>
 68    /// Sorts the elements of a sequence in ascending order according to a property path.
 69    /// </summary>
 70    /// <typeparam name="TEntity">The type of the sorted entity.</typeparam>
 71    /// <param name="source">A sequence of values to sort.</param>
 72    /// <param name="propertyPath">Path to the property to sort by.</param>
 73    /// <param name="configuration">Sort order configuration.</param>
 74    public static IOrderedQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string propertyPath, Sort
 13975        => source.OrderBy(nameof(Queryable.OrderBy), propertyPath, configuration);
 76
 77    /// <summary>
 78    /// Sorts the elements of a sequence in descending order according to a property path.
 79    /// </summary>
 80    /// <typeparam name="TEntity">The type of the sorted entity.</typeparam>
 81    /// <param name="source">A sequence of values to sort.</param>
 82    /// <param name="propertyPath">Path to the property to sort by.</param>
 83    /// <param name="configuration">Sort order configuration.</param>
 84    public static IOrderedQueryable<TEntity> OrderByDescending<TEntity>(this IQueryable<TEntity> source, string property
 17285        => source.OrderBy(nameof(Queryable.OrderByDescending), propertyPath, configuration);
 86
 87    /// <summary>
 88    /// Performs a subsequent ordering of the elements in a sequence in ascending order according to a property path.
 89    /// </summary>
 90    /// <typeparam name="TEntity">The type of the sorted entity.</typeparam>
 91    /// <param name="source">A sequence of values to sort.</param>
 92    /// <param name="propertyPath">Path to the property to sort by.</param>
 93    /// <param name="configuration">Sort order configuration.</param>
 94    public static IOrderedQueryable<TEntity> ThenBy<TEntity>(this IOrderedQueryable<TEntity> source, string propertyPath
 1895        => source.OrderBy(nameof(Queryable.ThenBy), propertyPath, configuration);
 96
 97    /// <summary>
 98    /// Performs a subsequent ordering of the elements in a sequence in descending order according to a property path.
 99    /// </summary>
 100    /// <typeparam name="TEntity">The type of the sorted entity.</typeparam>
 101    /// <param name="source">A sequence of values to sort.</param>
 102    /// <param name="propertyPath">Path to the property to sort by.</param>
 103    /// <param name="configuration">Sort order configuration.</param>
 104    public static IOrderedQueryable<TEntity> ThenByDescending<TEntity>(this IOrderedQueryable<TEntity> source, string pr
 12105        => source.OrderBy(nameof(Queryable.ThenByDescending), propertyPath, configuration);
 106
 107    private static IOrderedQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string methodName, strin
 108    {
 341109        var propertyPathParts = propertyPath.Split('.');
 341110        if (!propertyPathParts.Any())
 0111            throw new ArgumentException("Property path must not be empty.", nameof(propertyPath));
 112
 341113        var parameter = Expression.Parameter(typeof(TEntity), "x");
 341114        var useConditionalAccess = source.Provider.ConditionalAccessRequested(configuration);
 341115        var caseInsensitive = configuration.CaseInsensitivePropertyMatching;
 341116        var propertyAccess = (Expression)parameter;
 1746117        foreach (var part in propertyPathParts)
 118        {
 119            try
 120            {
 540121                propertyAccess = Add(propertyAccess, part, caseInsensitive, useConditionalAccess);
 524122            }
 16123            catch (ArgumentException ex) when (ex.Message.Contains("not found on type"))
 124            {
 16125                if (configuration.IgnoreParseExceptions)
 9126                    return source.OrderBy(x => 0);
 7127                throw;
 128            }
 129        }
 130
 325131        var propertyAccessLambda = Expression
 325132            .Lambda(propertyAccess, parameter);
 133
 325134        var orderByExpression = Expression
 325135            .Call(
 325136                type: typeof(Queryable),
 325137                methodName: methodName,
 325138                typeArguments: [typeof(TEntity), propertyAccess.Type],
 325139                arguments: [source.Expression, Expression.Quote(propertyAccessLambda)]
 325140            );
 141
 325142        return (IOrderedQueryable<TEntity>)source.Provider.CreateQuery<TEntity>(orderByExpression);
 9143    }
 144
 145    private static bool ConditionalAccessRequested(this IQueryProvider provider, SortConfiguration configuration)
 146    {
 341147        switch (configuration.UseConditionalAccess)
 148        {
 149            case SortConditionalAccess.Never:
 1150                return false;
 151            case SortConditionalAccess.Always:
 1152                return true;
 153            case SortConditionalAccess.WhenEnumerableQuery:
 339154                var providerType = provider.GetType();
 339155                var isEnumerableQueryProvider = providerType.IsGenericType && providerType.GetGenericTypeDefinition() ==
 166156                return isEnumerableQueryProvider;
 157            default:
 0158                throw new UnreachableException();
 159        }
 160    }
 161
 162    private static Expression Add(Expression memberAccess, string propertyName, bool caseInsensitive, bool useConditiona
 163    {
 540164        if (propertyName.EqualsOrdinal(PropertySort.PATH_TO_SELF))
 0165            return memberAccess;
 166
 540167        var memberType = memberAccess.Type;
 540168        var property = memberType.GetProperty(propertyName);
 540169        if (property == null && caseInsensitive)
 28170            property = memberType.GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags
 540171        if (property == null)
 16172            throw new ArgumentException($"Property '{propertyName}' not found on type '{memberType.Name}'.", nameof(prop
 173
 524174        var propertyAccess = Expression.MakeMemberAccess(memberAccess, property);
 175
 524176        var conditionalAccessRequired = useConditionalAccess && property.PropertyType.IsNullable();
 524177        if (!conditionalAccessRequired)
 268178            return propertyAccess;
 179
 256180        var memberNull = Expression.Constant(null, memberAccess.Type);
 256181        var memberIsNull = Expression.Equal(memberAccess, memberNull);
 256182        var propertyNull = Expression.Constant(null, property.PropertyType);
 256183        var conditionalPropertyAccess = Expression.Condition(memberIsNull, propertyNull, propertyAccess);
 256184        return conditionalPropertyAccess;
 185    }
 186}