I'm attempting to write a relatively simple but general pagination function for EFCore, and am having trouble finding the right function type to make sure that my sort-key selector is translated to SQL for both the Where and OrderBy clauses.
public List<TItem> GetPage<TItem>(IQueryable<TItem> items, TFunc keyExtractor, int? itemsAfter = default)
{
if (itemsAfter != default)
{
items = items.Where(item => keyExtractor(item) > itemsAfter);
}
var materialized = items.OrderBy(keyExtractor).Take(pageSize).ToList();
// ... stuff to trim page size ...
}
- when
TFuncisExpression<Func<TItem, int>>, it's properly handled inOrderBybut theWhereclause must be changed tokeyExtractor.Compile().Invoke(item) > itemsAfterand is not translated, giving me "Microsoft.EntityFrameworkCore.Query: Warning: The LINQ expression 'where (Convert(__Compile_1.Invoke([t]), Nullable`1) > __itemsAfter_2)' could not be translated and will be evaluated locally." - when
TFuncisFunc<TItem, int>, it is properly handled by theWhereclause but theOrderByusesIEnumerable.OrderByrather thanIQueryable.OrderBy. This means that the order & take are done locally - which is fine for the top-level object itself, but child properties do not take theTakeinto account and pull an enormous amount of data.
Is there a better way to convert one of these to the other, such that the query will be translated? Or is there a third type I can accept that could be easily made into these two types? My keyExtractor parameters are all relatively simple, e.g. item => item.id. Due to reasons outside of my control, I'm currently stuck with EFCore 2.1
Func<TItem, int>case seems very suspitious to actually handleWhereon client side.keyExtractoraccept just one field?keyExtractor-item => item.id. The extractor takes one TItem and returns an int.