【问题标题】:Dynamically Adding a GroupBy to a Lambda Expression将 GroupBy 动态添加到 Lambda 表达式
【发布时间】:2010-10-07 22:05:18
【问题描述】:

好的,我承认我还没有完全“获得” lambda 表达式和 LINQ 表达式树。我正在做的很多事情都是剪切和粘贴,看看什么是有效的。我查看了很多文档,但还没有找到我的“啊哈”时刻。

话虽这么说......

我正在尝试将 GroupBy 表达式动态添加到我的 Linq 表达式中。我在这里关注了这个问题: Need help creating Linq.Expression to Enumerable.GroupBy

并尝试实现我在那里看到的内容。

首先,我的数据库有实体类,还有一个名为ObjCurLocViewNormalized 的表

我有一个方法可以进行初始调用,

public IQueryable<ObjCurLocViewNormalized> getLocations()
{
    IQueryable<ObjCurLocViewNormalized> res = (from loc in tms.ObjCurLocViewNormalized
                               select loc);
    return res;
}

所以我可以打电话:

IQueryable<MetAmericanLinqDataModel.ObjCurLocViewNormalized> locations = american.getLocations();

目前没有问题。

现在,我想按任意列进行分组,调​​用如下:

var grouped = locations.addGroupBy(childLocationFieldName);

现在,我有一个方法:

static public System.Linq.IQueryable<System.Linq.IGrouping<string, TResult>> addGroupBy<TResult>(this IQueryable<TResult> query, string columnName)
{

    var providerType = query.Provider.GetType();
    // Find the specific type parameter (the T in IQueryable<T>)
    var iqueryableT = providerType.FindInterfaces((ty, obj) => ty.IsGenericType && ty.GetGenericTypeDefinition() == typeof(IQueryable<>), null).FirstOrDefault();
    var tableType = iqueryableT.GetGenericArguments()[0];
    var tableName = tableType.Name;

    var data = Expression.Parameter(iqueryableT, "query");
    var arg = Expression.Parameter(tableType, tableName);
    var nameProperty = Expression.PropertyOrField(arg, columnName);
    var lambda = Expression.Lambda<Func<TResult, string>>(nameProperty, arg);

    var expression = Expression.Call(typeof(Enumerable), 
                                    "GroupBy", 
                                    new Type[] { tableType, typeof(string) },
                                    data, 
                                    lambda);
    var predicate = Expression.Lambda<Func<TResult, String>>(expression, arg); // this is the line that produces the error I describe below
    var result = query.GroupBy(predicate).AsQueryable();
    return result;
}

所有这些都可以编译,但是当我运行它时,我得到了错误:

System.ArgumentException: Expression of type 'System.Collections.Generic.IEnumerable`1[System.Linq.IGrouping`2[System.String,MetAmericanLinqDataModel.ObjCurLocViewNormalized]]' cannot be used for return type 'System.String'

错误来自这一行:

 var predicate = Expression.Lambda<Func<TResult, String>>(expression, arg);

我正在从成功的工作中复制和调整这段代码,我在动态添加 Where 子句到表达式中所做的工作。所以我有点在黑暗中刺伤。

如果有人可以帮助阐明这一点,显然发布完整的工作代码并为我做所有的想法会很棒:),但如果你能说明为什么这是错误的,或者如何围绕这些概念来思考一下,那就太好了。如果您可以指出真正有助于弥合 lambda 表达式基础知识和构建动态表达式树之间差距的文档,那就太好了。我的知识显然存在很大的漏洞,但我认为这些信息可能对其他人有用。

感谢大家的时间,当然如果我在别处找到答案,我会在这里发布。

再次感谢。

【问题讨论】:

  • 为什么你的predicate 行使用Func&lt;TResult, string&gt;?您的表达返回一个IGrouping,它不是string
  • 感谢柯克,我试过:将行更改为: var predicate = Expression.Lambda>>(expression, arg);但现在我收到编译错误:错误 5 无法将类型 'System.Linq.IQueryable,TResult>>' 隐式转换为 'System.Linq.IQueryable>'。存在显式转换(您是否缺少演员表?)。这是写你告诉我的错误的方式吗?
  • 主要问题是您将.GroupBy 的调用与传递给it 的lambda 谓词混淆了。目前没有时间进一步回答,但如果这个问题在一个小时后仍未得到解答,我会进行更彻底的测试。
  • 啊,这个错误与返回类型不匹配有关。但我不认为我想更改返回类型,是吗?

标签: linq expression-trees


【解决方案1】:

解决方案应该很简单:

public static IQueryable<IGrouping<TColumn, T>> DynamicGroupBy<T, TColumn>(
    IQueryable<T> source, string column)
{
    PropertyInfo columnProperty = typeof(T).GetProperty(column);
    var sourceParm = Expression.Parameter(typeof(T), "x");
    var propertyReference = Expression.Property(sourceParm, columnProperty);
    var groupBySelector = Expression.Lambda<Func<T, TColumn>>(propertyReference, sourceParm);

    return source.GroupBy(groupBySelector);
}

假设这样的示例类:

public class TestClass
{
    public string TestProperty { get; set; }
}

你可以这样调用它:

var list = new List<TestClass>();
var queryable = list.AsQueryable();
DynamicGroupBy<TestClass, string>(queryable, "TestProperty");

【讨论】:

  • 看来你的答案和我的差不多,但是现在已经很全了,而且有代码重构=)
  • 嗯,看起来很有希望,但是“方法'参数'没有重载需要'1'参数。”所以它不喜欢这条线: var sourceParm = Expression.Parameter(typeof(T));我是3.5。这是 .Net 4.0 的东西吗?我马上就要升级了。
  • @donundeen,是的,这是 .Net 4 的东西,但这并不重要。只需将您认为有助于识别参数的任何字符串作为第二个参数传递,或者只是传递“x”。没关系。 :)
【解决方案2】:

您需要做的就是使其工作如下:

    static public IQueryable<IGrouping<TValue, TResult>> addGroupBy<TValue, TResult>(
                this IQueryable<TResult> query, string columnName)
            {
            var providerType = query.Provider.GetType();

            // Find the specific type parameter (the T in IQueryable<T>)
            const object EmptyfilterCriteria = null;
            var iqueryableT = providerType
                .FindInterfaces((ty, obj) => ty.IsGenericType && ty.GetGenericTypeDefinition() == typeof(IQueryable<>), EmptyfilterCriteria)
                .FirstOrDefault();
            Type tableType = iqueryableT.GetGenericArguments()[0];
            string tableName = tableType.Name;

            ParameterExpression data = Expression.Parameter(iqueryableT, "query");
            ParameterExpression arg = Expression.Parameter(tableType, tableName);
            MemberExpression nameProperty = Expression.PropertyOrField(arg, columnName);
            Expression<Func<TResult, TValue>> lambda = Expression.Lambda<Func<TResult, TValue>>(nameProperty, arg);
            //here you already have delegate in the form of "TResult => TResult.columnName"
            return query.GroupBy(lambda);

        /*var expression = Expression.Call(typeof(Enumerable), 
                                        "GroupBy", 
                                        new Type[] { tableType, typeof(string) },
                                        data, 
                                        lambda);
        var predicate = Expression.Lambda<Func<TResult, String>>(expression, arg); // this is the line that produces the error I describe below
        var result = query.GroupBy(predicate).AsQueryable();
        return result;*/
    }

你会用以下方式称你为表达式:

var grouped = locations.addGroupBy<string, ObjCurLocViewNormalized>(childLocationFieldName);

第一个通用参数“字符串”我们用于明确说明您分组的元素类型。例如,您可以按“int”字段分组,方法调用如下:

var grouped = locations.addGroupBy<int, ObjCurLocViewNormalized>(someFieldNameWithTheTypeOfInt);

编辑 只是为了完成这个解决方案你的方式

 //return query.GroupBy(lambda);
    MethodCallExpression expression = Expression.Call(typeof (Enumerable),
                                                      "GroupBy",
                                                      new[] { typeof(TResult), typeof(TValue) },
                                                      data,
                                                      lambda);

    var result = Expression.Lambda(expression, data).Compile().DynamicInvoke(query);
    return ((IEnumerable<IGrouping<TValue, TResult>>)result).AsQueryable();

【讨论】:

  • 为什么在// Find the specific type parameter (the T in IQueryable&lt;T&gt;) 的评论后面会有那个时髦的代码? Surly 你已经在TResult 中获得了这些信息,不是吗?
  • 我更喜欢不要接触原始代码,因为问题是关于其他事情的。当然可以改进代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-25
  • 2016-12-04
  • 2018-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多