Search code examples
c#linqexpression-treesiqueryable

IQueryable GroupBy Lambda expression fails for primitive types with object key


I am in process of creating Lambda Expression for an IQueryable<TSource>, following is my extension method code, which I need to call like:

queryableData.GroupBy<int,Product>("ID")

queryableData.GroupBy<string,Product>("Name")

public static IQueryable<IGrouping<TKey,TSource>> GroupBy<TKey,TSource>(this IQueryable<TSource> queryable, string propertyName)
    {
        // Access the propertyInfo, using Queryable Element Type (Make it Case insensitive)
        var propInfo = queryable.ElementType.GetProperty(propertyName,BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

        // Access the Collection / Queryable Type
        var collectionType = queryable.ElementType;

        // Creating Group Parameter Expression
        var groupParameterExpression = Expression.Parameter(collectionType, "g");

        // Create MemberEXpression with the Property (access the property of a Type)
        var propertyAccess = Expression.MakeMemberAccess(groupParameterExpression, propInfo);

        // Create Lambda Expression
        var lambdaExpression = Expression.Lambda<Func<TSource,TKey>>(propertyAccess, groupParameterExpression);

        // Return GroupBy result
        return queryable.GroupBy(lambdaExpression);
    }

My aim is to generate Expression<Func<TSource,object>> instead of Expression<Func<TSource,TKey>>, so that it can be called without providing the Key type, following is code:

public static IQueryable<IGrouping<object, TSource>> GroupByObjectKey<TSource>(this IQueryable<TSource> queryable, string propertyName)
    {
        // Access the propertyInfo, using Queryable Element Type (Make it Case insensitive)
        var propInfo = queryable.ElementType.GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

        // Access the Collection / Queryable Type
        var collectionType = queryable.ElementType;

        // Creating Group Parameter Expression
        var groupParameterExpression = Expression.Parameter(collectionType, "g");

        // Create MemberEXpression with the Property (access the property of a Type)
        var propertyAccess = Expression.MakeMemberAccess(groupParameterExpression, propInfo);

        // Create Lambda Expression
        var lambdaExpression = Expression.Lambda<Func<TSource, object>>(propertyAccess, groupParameterExpression);

        // Return GroupBy result
        return queryable.GroupBy(lambdaExpression);
    }

Now I able to make it work for string type as follows:

queryableData.GroupBy<Product>("Name")

but it fails on following call for integer type with exception as stated underneath:

queryableData.GroupBy<Product>("Id")

Expression of type 'System.Int32' cannot be used for return type 'System.Object'

This is a clear case of Type conversion, but I am surprised that why a type would refuse to be converted to Object base class, what could be the rationale, any pointer / suggestion


Solution

  • As comments already pointed out, you need to add a conversion to object:

    var convertToObject = Expression.Convert(propertyAccess, typeof(object));
    
    var lambdaExpression = Expression.Lambda<Func<TSource, object>>(convertToObject, groupParameterExpression);