Search code examples
c#linqiorderedqueryable

Create an IOrderedQueryable and use it on another List


I create an IOrderedQueryable and want to use the query on different lists later in the process. But all I get are empty results.

My code for creating the query:

IOrderedQueryable<SomeElement> sortingQuery;

if (userInputA)
{
    sortingQuery = new List<SomeElement>().AsQueryable()
        .OrderBy(clt => clt.PropertyA);
}
else
{
    sortingQuery = new List<SomeElement>().AsQueryable()
        .OrderBy(clt => cltPropertyB);
}

sortingQuery = userInputB 
    ? sortingQuery.ThenBy(clt => clt.PropertyC ?? int.MaxValue) 
    : sortingQuery.ThenByDescending(clt => clt.PropertyD ?? int.MaxValue);

sortingQuery = sortingQuery.ThenBy(clt => clt.PropertyZ);

Later I want to execute the query on n lists:

while(process)
{
    List<SomeElement> data = await GetNewData();

    var orderedData = data.AsQueryable()
           .Provider
           .CreateQuery<SomeElement>(sortingQuery.Expression)
           .ToList();

    //...
}

Unfortunately, the orderedData list is always empty, regardless of the data.Count().

What am I doing wrong?


Solution

  • I would encapsulate everything into helper classes to simplify usage. However, I do not recommend using EnumerableQuery if performance is a concern.

    Updated Code Using New Extension Methods:

    var sortingQuery = Enumerable.Empty<SomeElement>().CreateTemplateQuery();
    
    if (userInputA)
        sortingQuery = sortingQuery.OrderBy(clt => clt.PropertyA);
    else
        sortingQuery = sortingQuery.OrderBy(clt => clt.PropertyB);
    
    sortingQuery = userInputB
        ? sortingQuery.ThenBy(clt => clt.PropertyC ?? int.MaxValue)
        : sortingQuery.ThenByDescending(clt => clt.PropertyD ?? int.MaxValue);
    
    sortingQuery = sortingQuery.ThenBy(clt => clt.PropertyZ);
    

    Reusing the Sorting Logic Later:

    while (process)
    {
        List<SomeElement> data = await GetNewData();
    
        var orderedData = data.ApplyOrderTemplate(sortingQuery)
           .ToList();
    
        // Further processing...
    }
    

    Implementation of Helper Classes:

    public static class OrderingHelper
    {
        static readonly IQueryProvider EnumerableProvider = Array.Empty<int>().AsQueryable().Provider;
    
        static class OrderHelperImpl<T>
        {
            // A placeholder that can be easily replaced
            public static ParameterExpression Anchor = Expression.Parameter(typeof(IQueryable<T>), "anchor");
    
            public static IOrderedQueryable<T> CreateTemplateQuery()
            {
                return (IOrderedQueryable<T>)EnumerableProvider.CreateQuery<T>(Anchor);
            }
        }
    
        public static IOrderedQueryable<T> CreateTemplateQuery<T>(this IEnumerable<T> enumerable)
        {
            return OrderHelperImpl<T>.CreateTemplateQuery();
        }
    
        public static IOrderedQueryable<T> ApplyOrderTemplate<T>(this IEnumerable<T> source, IQueryable<T> template)
        {
            var visitor = new ReplacingVisitor(OrderHelperImpl<T>.Anchor, source.AsQueryable().Expression);
            var newExpression = visitor.Visit(template.Expression);
            return (IOrderedQueryable<T>)EnumerableProvider.CreateQuery<T>(newExpression);
        }
    
        class ReplacingVisitor : ExpressionVisitor
        {
            private readonly Expression _original;
            private readonly Expression _replacement;
    
            public ReplacingVisitor(Expression original, Expression replacement)
            {
                _original = original;
                _replacement = replacement;
            }
    
            [return: NotNullIfNotNull(nameof(node))]
            public override Expression? Visit(Expression? node)
            {
                return node == _original ? _replacement : base.Visit(node);
            }
        }
    }
    

    Why This Approach Is Inefficient:

    This method is not ideal for performance because EnumerableProvider dynamically generates a compiled delegate for each enumeration, leading to unnecessary overhead.