3

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

Mrinal Kamboj
  • 11,300
  • 5
  • 40
  • 74
  • would [this](https://stackoverflow.com/q/32146571/1132334) apply here? and [this](https://stackoverflow.com/a/2200247/1132334) of course – Cee McSharpface Sep 29 '17 at 12:54
  • Second one does, but I am not sure why is it required for conversion to Object type of all things.It works for reference type but not for primitive types. – Mrinal Kamboj Sep 29 '17 at 13:10
  • 1
    I'd have a go at an answer along the line of "explicit conversion is required here", but cannot say it any better than [Eric Lippert here](https://blogs.msdn.microsoft.com/ericlippert/2009/08/06/not-everything-derives-from-object/); *derives from* is not the same as *convertible to*: "The way to correct this myth is to simply replace "derives from" with "is convertible to", and to ignore pointer types: every non-pointer type in C# is *convertible* to object" – Cee McSharpface Sep 29 '17 at 13:16
  • That's one of the gotchas of expressions: value type boxing requires an explicit `Convert` instruction. It's similar to what the compiler does for you when you write code such as `return (object)0;`. Just embrace the fact that value types are different, and run with it. – Kirill Shlenskiy Sep 29 '17 at 13:29
  • Other commenters already mentioned that you need to wrap value type returning expression (`propertyAccess`) with `Expression.Convert`. This will allows you to successfully build you query. But please note that if you are planning to use this in EF6 query, you'll get simply runtime exception *Unable to cast the type 'System.Int32' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.*. In general variance doesn't work with value types. There is a reason for `GroupBy` having `TKey` argument. – Ivan Stoev Sep 29 '17 at 15:08

1 Answers1

1

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);
svick
  • 236,525
  • 50
  • 385
  • 514