Open zbarnett opened 1 year ago
@zbarnett Can you explain a bit more about why you want to do this?
Sure! We have some generic code that is helping with our searching/sorting/pagination and then we have more specific code for each of the entities we want to allow those operations on. This is mainly affecting us with sorting/searching.
public static IQueryable<T> BuildSortQuery<T>(IQueryable<T> query, Dictionary<string, Expression<Func<T, IComparable>>> expressions, JqueryDataTablesParameters table)
{
foreach (var order in table.Order)
{
if (table.Columns[order.Column] != null)
{
var column = table.Columns[order.Column];
if (expressions.TryGetValue(column.Data, out Expression<Func<T, IComparable>> expression))
{
if (expression != null)
{
query = query.ApplySorting(expression, order.Dir);
}
}
}
}
return query;
}
public static IQueryable<T> ApplySorting<T>(this IQueryable<T> query, Expression<Func<T, IComparable>> expression, DTOrderDir order)
{
var ordered = query as IOrderedQueryable<T>;
if (order == DTOrderDir.ASC)
{
if (query.Expression.Type == typeof(IOrderedQueryable<T>))
return ordered.ThenBy(expression);
return query.OrderBy(expression);
}
else
{
if (query.Expression.Type == typeof(IOrderedQueryable<T>))
return ordered.ThenByDescending(expression);
return query.OrderByDescending(expression);
}
}
public static IQueryable<MyCustomType> BuildSortQuery(IQueryable<MyCustomType> query, JqueryDataTablesParameters table)
{
var sortExpressions = new Dictionary<string, Expression<Func<MyCustomType, IComparable>>>
{
["Name"] = x => x.Name,
["Description"] = x => x.Description
};
return QueryBuilderHelper.BuildSortQuery(query, sortExpressions, table);
}
I am currently working around this issue by just changing the types in these methods from IComparable
to object
but it would be nice to have some compile-time guarantee that the field we're trying to sort on is comparable.
Thanks for the additional info. We will discuss this, but don't you worry that using IComparable
in the signature implies that the type implementing the interface will have some impact on the behavior? That is, that it might, somehow, have an effect on the way the database does comparisons?
Yes, and I would expect the type implementing the interface could have an affect on the behavior. The piece of this that I left out since it didn't seem immediately relevant was that we're using a lot of custom types with value conversions and we want to make sure that those types are the ones implementing the required interface(s) before they can be used for searching/sorting.
But nothing that you do with the type, including value converters or comparers, will affect what happens in the database, which is where the sorting will happen after the IQueryable is translated.
Currently, yes. But once https://github.com/dotnet/efcore/issues/9906 is addressed I could imagine that it would since I'm not sure if there would be an obvious way (by default) to have the database order by a struct with multiple fields.
I don't believe the current plans for https://github.com/dotnet/efcore/issues/9906 include this, but I will discuss with the team.
I discussed #9906 with the team, and it's likely that ordering by value objects that map to multiple columns will not be supported. A value object mapped to a single column will work as it does now, but obviously isn't affected in any way by the IComparable
interface.
I recently ran into a bit of unexpected behavior where casting to
IComparable
orIEquatable<>
causes a previously working query to become untranslatable. It's especially unexpected that casting toobject
still works butIComparable
does not.I have restructured my code to work around this quirk but wanted to put it out there since I couldn't find an existing issue for it.
Minimal reproducible example
Stack trace
Include provider and version information
EF Core version: 7.0.2 & 6.0.13 Database provider: Microsoft.EntityFrameworkCore.SqlServer & Microsoft.EntityFrameworkCore.Sqlite Target framework: .NET 6.0 & .NET 7.0 Operating system: Windows 10 IDE: Visual Studio 2022 17.4.4