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?
I would encapsulate everything into helper classes to simplify usage. However, I do not recommend using EnumerableQuery
if performance is a concern.
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);
while (process)
{
List<SomeElement> data = await GetNewData();
var orderedData = data.ApplyOrderTemplate(sortingQuery)
.ToList();
// Further processing...
}
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);
}
}
}
This method is not ideal for performance because EnumerableProvider
dynamically generates a compiled delegate for each enumeration, leading to unnecessary overhead.