在表达式lambda中调用方法

时间:2016-06-06 09:41:45

标签: c# linq lambda expression-trees

我试图用表达式树模拟这个lambda调用:

myList.AsQueryable().GroupBy(g=>g.Name).Select(s => s.FirstOrDefault());

到目前为止,我来到这里:

public Expression Distinct (IQueryable queryable, string propertyName)
{
    var propInfo = queryable.ElementType.GetProperty(propertyName);
    var collectionType = queryable.ElementType;

    var groupParameterExpression = Expression.Parameter(collectionType, "g");
    var propertyAccess = Expression.MakeMemberAccess(groupParameterExpression, propInfo);
    var groupLambda = Expression.Lambda(propertyAccess, groupParameterExpression);
    var groupExpression = Expression.Call(typeof(Queryable), "GroupBy", new Type[] { collectionType, propInfo.PropertyType }, queryable.Expression, groupLambda);

    var selectParameterExpression = Expression.Parameter(groupExpression.Type, "s");
    var selectFirstOrDefaultMethodExpression = Expression.Call(typeof(Enumerable), "FirstOrDefault", new Type[] { collectionType }, selectParameterExpression);
    var selectLambda = Expression.Lambda(selectFirstOrDefaultMethodExpression, selectParameterExpression);
    return Expression.Call(typeof(Queryable), "Select", new Type[] { groupExpression.Type, selectParameterExpression.Type }, groupExpression, selectLambda);
}

这部分可以接受:

var groupParameterExpression = Expression.Parameter(collectionType, "g");
var propertyAccess = Expression.MakeMemberAccess(groupParameterExpression, propInfo);
var groupLambda = Expression.Lambda(propertyAccess, groupParameterExpression);
var groupExpression = Expression.Call(typeof(Queryable), "GroupBy", new Type[] { collectionType, propInfo.PropertyType }, queryable.Expression, groupLambda);

我在groupExpression中获得了一个有效的表达式,并且在这种情况下从模型中调用type属性时收到Name

IGrouping<string, Product>其中,Product是一个如下所示的模型:

public class Product
{
    public string Name { get; set; }
}

selectParameterExpression是一个类型为IGrouping<string,Product>的参数表达式。

当我尝试在此处拨打FirstOrDefault时出现异常:

var selectFirstOrDefaultMethodExpression = Expression.Call(typeof(Enumerable), "FirstOrDefault", new Type[] { collectionType }, selectParameterExpression);
  

没有通用的方法&#39; FirstOrDefault&#39; on type&#39; System.Linq.Enumerable&#39;与提供的类型参数和参数兼容。如果方法是非泛型的,则不应提供类型参数。

1 个答案:

答案 0 :(得分:2)

该代码存在几个问题。

首先,在使用Expression.Call调用Queryable方法时,您必须使用Expression.Quote包含lambdas,以便将它们视为Expression<Func<...>>而不仅仅Func<...> {1}}。

其次,当您传递Select IGrouping<TKey, TElement>时,示例中的groupExpression.Type参数类型应为IQueryable<IGrouping<TElement, TKey>>,即您需要提取IGrouping部分,例如:

groupExpression.Type.GetGenericArguments().Single()

最后,Select调用通用参数应为IGrouping<TKey, TElement>TElement,例如可以从selectParameterExpression.TypeselectLambda.Body.Type获取。

所以工作方法可能是这样的:

public Expression Distinct(IQueryable queryable, string propertyName)
{
    var propInfo = queryable.ElementType.GetProperty(propertyName);
    var collectionType = queryable.ElementType;

    var groupParameterExpression = Expression.Parameter(collectionType, "g");
    var propertyAccess = Expression.MakeMemberAccess(groupParameterExpression, propInfo);
    var groupLambda = Expression.Lambda(propertyAccess, groupParameterExpression);
    var groupExpression = Expression.Call(typeof(Queryable), "GroupBy", new Type[] { collectionType, propInfo.PropertyType }, queryable.Expression, Expression.Quote(groupLambda));

    var selectParameterExpression = Expression.Parameter(groupExpression.Type.GetGenericArguments().Single(), "s");
    var selectFirstOrDefaultMethodExpression = Expression.Call(typeof(Enumerable), "FirstOrDefault", new Type[] { collectionType }, selectParameterExpression);
    var selectLambda = Expression.Lambda(selectFirstOrDefaultMethodExpression, selectParameterExpression);
    return Expression.Call(typeof(Queryable), "Select", new Type[] { selectParameterExpression.Type, selectLambda.Body.Type }, groupExpression, Expression.Quote(selectLambda));
}