【问题标题】:How to select Item from an property list using Expression Tree in c#如何使用 C# 中的表达式树从属性列表中选择项目
【发布时间】:2019-12-19 06:23:50
【问题描述】:

我有一个如下的 lambda 表达式:

var source = new List<Entidade>();

var z = source.Select<Entidade, Resultado>(
                s =>
                new Resultado
                    {
                        Detalhes =
                            new List<DetalheResultado>(
                            s.Detalhes.Select<Detalhe, DetalheResultado>(
                                t => new DetalheResultado { Id = t.Id, Valor = t.Valor }))
                    });

我正在尝试使用以下代码使用表达式执行相同的查询:

ParameterExpression sourceItem = Expression.Parameter(typeof(Entidade), "s");

var source3 = Expression.Parameter(typeof(Detalhe), "t");
var property3 = typeof(DetalheResultado).GetProperty("Id");
var member3 = Expression.Property(source3, "Id");
var itemResult3 = Expression.New(typeof(DetalheResultado).GetConstructor(Type.EmptyTypes));
var memberBinding3 = Expression.Bind(property3, member3);
var memberInit3 = Expression.MemberInit(itemResult3, memberBinding3);
var selector3 = Expression.Lambda(memberInit3, source3);

var detalhes = Expression.Property(sourceItem, "Detalhes");

// here you get an error
var lista3 = Expression.Call(
    typeof(Queryable), 
    "Select", 
    new Type[] { typeof(Detalhe), typeof(DetalheResultado) },
    detalhes, 
    selector3);

var listaResultado = typeof(DetalheResultado).GetProperty("Detalhes");
var memberBindigs4 = Expression.Bind(listaResultado, lista3);

...

但是运行这段代码我得到了错误:

“System.Linq.Queryable”类型上没有通用方法“Select” 与参数和提供的类型参数兼容。任何 如果方法不是通用的,则必须提供参数。

我查阅了 DebugView 表达式并实现了表达式作为它的返回值,但得到了上述错误。

有什么建议吗?

【问题讨论】:

  • 您是否也需要使用表达式树来完成Select 调用?制作投影表达式并在常规方法调用中调用Select 要容易得多。
  • Queryable.Select 的第一个参数应该是 IQueryable,但似乎 s.DetalhesIEnumerable。你能提供类定义吗?
  • @Grundy,你是对的。 s.Detalhes 是 IEnumerable。将 typeof(Queryable) 更改为 typeof(Enumerable) 修复了该错误。

标签: c# linq expression-trees


【解决方案1】:

我从来没有在 LINQ 泛型方法上使用 Expression.Call 方法。我总是单独获取它(参见变量firstSelectMethodsecondSelectMethod)。我不知道为什么,如果其他人知道为什么这不起作用,我将非常感激。以下代码有效,尽管我对您的类的外观做了一些假设。

请注意,我用Queryable 替换了Enumerable

var paramS = Expression.Parameter(typeof(Entidade), "s");
var paramT = Expression.Parameter(typeof(Detalhe), "t");

var firstSelectMethod = typeof(Enumerable).GetMethods().First(m => m.Name == "Select").MakeGenericMethod(typeof(Entidade), typeof(Resultado));
var secondSelectMethod = typeof(Enumerable).GetMethods().First(m => m.Name == "Select").MakeGenericMethod(typeof(Detalhe), typeof(DetalheResultado));

var lista4 = Expression.Call(
    firstSelectMethod,
    Expression.Constant(source),
    Expression.Lambda(
        Expression.MemberInit(
            Expression.New(typeof(Resultado).GetConstructor(Type.EmptyTypes)), 
            Expression.Bind(
                typeof(Resultado).GetProperty("Detalhes"), 
                Expression.New(
                    typeof(List<DetalheResultado>).GetConstructor(new Type[] {typeof(IEnumerable<DetalheResultado>)}),
                    Expression.Call(
                        secondSelectMethod,
                        Expression.Property(
                            paramS,
                            "Detalhes"
                        ),
                        Expression.Lambda(
                            Expression.MemberInit(
                                Expression.New(typeof(DetalheResultado).GetConstructor(Type.EmptyTypes)), 
                                Expression.Bind(
                                    typeof(DetalheResultado).GetProperty("Id"),
                                    Expression.Property(paramT, "Id")
                                ),
                                Expression.Bind(
                                    typeof(DetalheResultado).GetProperty("Valor"),
                                    Expression.Property(paramT, "Valor")
                                )
                            ),
                            paramT
                        )
                    )
                )
            )
        ), 
        paramS
    )
);

【讨论】:

  • 为什么用Queryable 代替Enumerable
  • 示例代码使用了List&lt;T&gt;,它不会直接转换为IQueryable&lt;T&gt;。你可以打电话给AsQueryable(),但这只会让表达式变大。
  • 非常感谢。代码很清晰,工作正常
【解决方案2】:

(免责声明:我是相关库的作者。)

我已经写了a library that takes an expression tree and returns a string representation,也可以通过NuGet 获得。该库允许您查看用于生成表达式的工厂方法调用。

例如,您可以编写以下代码:

var source = new List<Entidade>();

Expression<Action> expr = () => source.Select<Entidade, Resultado>(
    s =>
        new Resultado {
            Detalhes = new List<DetalheResultado>(
                s.Detalhes.Select<Detalhe, DetalheResultado>(
                    t => new DetalheResultado { Id = t.Id, Valor = t.Valor }
                )
            )
        }
);

Console.WriteLine(expr.ToString("Factory methods"));

并取回以下输出:

// using static System.Linq.Expressions.Expression

Lambda(
    Call(
        typeof(Enumerable).GetMethod("Select"),
        source,
        Lambda(
            MemberInit(
                New(
                    typeof(Resultado).GetConstructor()
                ),
                Bind(
                    typeof(Resultado).GetProperty("Detalhes"),
                    New(
                        typeof(List<DetalheResultado>).GetConstructor(),
                        Call(
                            typeof(Enumerable).GetMethod("Select"),
                            MakeMemberAccess(s,
                                typeof(Entidade).GetProperty("Detalhes")
                            ),
                            Lambda(
                                MemberInit(
                                    New(
                                        typeof(DetalheResultado).GetConstructor()
                                    ),
                                    Bind(
                                        typeof(DetalheResultado).GetProperty("Id"),
                                        MakeMemberAccess(t,
                                            typeof(Detalhe).GetProperty("Id")
                                        )
                                    ),
                                    Bind(
                                        typeof(DetalheResultado).GetProperty("Valor"),
                                        MakeMemberAccess(t,
                                            typeof(Detalhe).GetProperty("Valor")
                                        )
                                    )
                                ),
                                var t = Parameter(
                                    typeof(Detalhe),
                                    "t"
                                )
                            )
                        )
                    )
                )
            ),
            var s = Parameter(
                typeof(Entidade),
                "s"
            )
        )
    )
)

如果您插入自己的类,您可能会得到更好的结果。我使用的类都是Visual Studio自动生成的,如下:

internal class Detalhe {
    public object Id { get; internal set; }
    public object Valor { get; internal set; }
}

internal class DetalheResultado {
    public object Id { get; internal set; }
    public object Valor { get; internal set; }
}

internal class Resultado {
    public List<DetalheResultado> Detalhes { get; internal set; }
}

internal class Entidade {
    public IEnumerable<Detalhe> Detalhes { get; internal set; }
}

【讨论】:

    猜你喜欢
    • 2016-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多