2

I am trying to add the ThenById() method which will be launched after a call to OrderBy() on IOrderedQueryable:

public static IOrderedQueryable<TEntity> ThenById<TEntity>(this IQueryable<TEntity> source)
{
    if (source == null)
    {
        throw new ArgumentNullException(nameof(source));
    }

    var command = "ThenBy";
    var thenByProperty = "Id";
    var type = typeof(TEntity);

    if (type.GetProperty(thenByProperty) == null)
    {
        throw new MissingFieldException(nameof(thenByProperty));
    }

    var param = Expression.Parameter(type, "p");

    var property = type.GetProperty(thenByProperty, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

    var propertyAccess = Expression.MakeMemberAccess(param, property);
    var orderByExpression = Expression.Lambda(propertyAccess, param);

    var resultExpression = Expression.Call(
        typeof(IOrderedQueryable),
        command,
        new Type[] { type, property.PropertyType },
        source.Expression,
        Expression.Quote(orderByExpression));
    return (IOrderedQueryable<TEntity>)source.Provider.CreateQuery<TEntity>(resultExpression);
}

I receive the following error message:

No generic method 'ThenBy' on type 'System.Linq.IOrderedQueryable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.

Lews Therin
  • 3,707
  • 2
  • 27
  • 53
kosnkov
  • 5,609
  • 13
  • 66
  • 107
  • 2
    Not sure if this is the cause of your issue, but shouldn't you use an `System.Linq.IOrderedQueryable` as the `this` parameter of your extension method ? – Pac0 Mar 21 '19 at 14:59
  • 2
    `ThenBy` is an extension method on `System.Linq.Queryable` – DavidG Mar 21 '19 at 15:00
  • 2
    also, the type is a generic type, I think you should try to `Call` your expression on an `IOrderedQueryable`, not a simple `IOrderedQueryable` – Pac0 Mar 21 '19 at 15:01
  • @DavidG oh, ok, my bad then. – Pac0 Mar 21 '19 at 15:01
  • as per my last comment, could you try to use : `typeof(IOrderedQueryable<>).MakeGenericType(new Type[] { TEntity })` instead of `typeof(IOrderedQueryable)` ? – Pac0 Mar 21 '19 at 15:03
  • 1
    Note that this will fail at runtime if `TEntity` doesn't have an `Id` property. My preference would be to give all entities with the `Id` property an interface (e.g. `IHasId`) and use a generic constraint here. That way you avoid expressions completely and get compile time safety. – DavidG Mar 21 '19 at 15:04
  • typeof(IOrderedQueryable) is the same error, typeof(IOrderedQueryable<>).MakeGenericType(new Type[] { TEntity }) gives TEntity is a type which is not valid a this context – kosnkov Mar 21 '19 at 15:05
  • @kosnkov : sorry, I can't try right now myself. Could you then try : `typeof(IOrderedQueryable<>).MakeGenericType(new Type[] { TEntity }).GetType()` – Pac0 Mar 21 '19 at 15:07
  • @kosnkov sorry ! i misread the error . you should use : `typeof(IOrderedQueryable<>).MakeGenericType(new Type[] { typeof(TEntity) })`, maybe with `GetType()`at the end as well – Pac0 Mar 21 '19 at 15:10
  • @Pac0 with typeof(IOrderedQueryable<>).MakeGenericType(new Type[] { typeof(TEntity) }) the same exception – kosnkov Mar 21 '19 at 15:10
  • 1
    I'm out of suggestions right now :/ . I remember trying to call extension methods by reflection, mixed with generics, is slightly tricky... – Pac0 Mar 21 '19 at 15:11

1 Answers1

3

The ThenBy extension method is in the System.Linq.Queryable class, not in IOrderedQueryable. You simply need to replace that in your code:

public static IOrderedQueryable<TEntity> ThenById<TEntity>(
    this IOrderedQueryable<TEntity> source)
{
    //snip

    var resultExpression = Expression.Call(
        typeof(System.Linq.Queryable),
        command,
        new Type[] { type, property.PropertyType },
        source.Expression,
        Expression.Quote(orderByExpression));
    return (IOrderedQueryable<TEntity>)source.Provider.CreateQuery<TEntity>(resultExpression);
}

Note that the method should be extending IOrderedQueryable, not just IQueryable.

However, this will fail at runtime if TEntity doesn't have an Id property. My preference would be to give all entities with the Id property an interface and use a generic constraint here. That way you avoid expressions completely and get compile time safety. For example:

public interface IHasId
{
    int Id { get; set; }
}

public class SomeEntity : IHasId
{
    public int Id { get; set; }
    public string Name { get; set; }
    //etc
}

Which simplifies your extension method:

public static IOrderedQueryable<TEntity> ThenById<TEntity>(
    this IOrderedQueryable<TEntity> source)
        where TEntity : IHasId
{
    return source.ThenBy(e => e.Id);
}
DavidG
  • 113,891
  • 12
  • 217
  • 223