OrderBy gets overriden when used multiple times
1. Description
When OrderBy is used multiple times, only the last one is applied
3. Fiddle or Project
Simplified test cases
private class Sub( int mainId, int other )
{
public int MainId { get; } = mainId;
public int Other { get; } = other;
}
// fail
[Fact]
public void OrderBy_multiple()
{
// Arrange
List<Sub> subList = [new Sub( 3, 1 ), new Sub( 1, 3 ), new Sub( 2, 2 )];
// Act
var query = subList.AsQueryable().OrderBy( "Other" ).OrderBy( "MainId" );
var result = query.ToList();
// Assert
result.ShouldBeInOrder( Shouldly.SortDirection.Descending, x => x.MainId );
}
// correct
[Fact]
public void OrderBy_single()
{
// Arrange
List<Sub> subList = [new Sub( 3, 1 ), new Sub( 1, 3 ), new Sub( 2, 2 )];
// Act
var query = subList.AsQueryable().OrderBy( "Other, MainId" );
var result = query.ToList();
// Assert
result.ShouldBeInOrder( Shouldly.SortDirection.Descending, x => x.MainId );
}
would expect both versions to behave identical.
And here is the (simplified) real case
private class Main( int id )
{
public int Id { get; } = id;
}
private class Sub( int mainId, int other )
{
public int MainId { get; } = mainId;
public int Other { get; } = other;
}
[Fact]
public void OrderBy_outer_and_inner()
{
// Arrange
List<Main> mainList = [new Main( 2 ), new Main( 1 ), new Main( 3 )];
List<Sub> subList = [new Sub( 3, 1 ), new Sub( 1, 3 ), new Sub( 2, 2 )];
// Act
IQueryable<Main> query = mainList.AsQueryable().Join(
subList.AsQueryable(),
outer => new { F1 = outer.Id },
inner => new { F1 = inner.MainId },
( outer, inner ) => new { o = outer, i = inner }
)
.OrderBy( "i.Other" )
.Select( x => x.o )
.OrderBy( "Id" );
List<Main> result = query.ToList();
// Assert
result.ShouldBeInOrder( Shouldly.SortDirection.Descending, x => x.Id );
}
note: the inner part (join+order by) and outer part(order by) are done by different parts of the program. note 2: the ShouldBeInOrder method is a custom extension, but i think you understand what result is expected ( behavior is identical with ef core queries )
If you have multiple OrderBy, you need to use ThenBy.
See
public void ThenBy_Dynamic()
@StefH thanks for the fast response this works for the simplified version - but it throws an exception in the real case example
System.InvalidOperationException
No generic method 'ThenBy' on type 'System.Linq.Queryable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.
at System.Linq.Expressions.Expression.FindMethod(Type type, String methodName, Type[] typeArgs, Expression[] args, BindingFlags flags)
at System.Linq.Expressions.Expression.Call(Type type, String methodName, Type[] typeArguments, Expression[] arguments)
at System.Linq.Dynamic.Core.DynamicQueryableExtensions.InternalThenBy(IOrderedQueryable source, ParsingConfig config, String ordering, IComparer comparer, Object[] args)
at System.Linq.Dynamic.Core.DynamicQueryableExtensions.ThenBy(IOrderedQueryable source, ParsingConfig config, String ordering, Object[] args)
at System.Linq.Dynamic.Core.DynamicQueryableExtensions.ThenBy[TSource](IOrderedQueryable`1 source, ParsingConfig config, String ordering, Object[] args)
at System.Linq.Dynamic.Core.DynamicQueryableExtensions.ThenBy[TSource](IOrderedQueryable`1 source, String ordering, Object[] args)
updated complex example
[Fact]
public void OrderBy_outer_and_inner()
{
// Arrange
List<Main> mainList = [new Main( 2 ), new Main( 1 ), new Main( 3 )];
List<Sub> subList = [new Sub( 3, 1 ), new Sub( 1, 3 ), new Sub( 2, 2 )];
// Act
IQueryable<Main> query = mainList.AsQueryable().Join(
subList.AsQueryable(),
outer => new { F1 = outer.Id },
inner => new { F1 = inner.MainId },
( outer, inner ) => new { o = outer, i = inner }
)
.OrderBy( "i.Other" )
.Select( x => x.o );
if( query is IOrderedQueryable<Main> orderedByQuery )
{
query = orderedByQuery.ThenBy( "Id" );
}
List<Main> result = query.ToList();
// Assert
result.ShouldBeInOrder( Shouldly.SortDirection.Descending, x => x.Id );
}
do you have any suggestions how this can be achieved ? (as mentioned, the inner join+order and the outer order are done in different parts of the application and doesnt know of each other - there might be multiple join parts)
it seems that its not possible with the native OrderBy and Thenby either. Im currenlty waiting for an answer from the dotnet team, how this can be achieved and will keep this ticket updated
@puschie286 Can this question be closed?
yes - the dotnet team said it is by design. "Select" removes all ordering (except if its the last command), so you have to create an object with references to all related entities before applying all ordering and your final select.