【问题标题】:Building nested conditional expression-trees构建嵌套条件表达式树
【发布时间】:2017-03-31 11:10:18
【问题描述】:

我正在尝试根据给定的配置动态构建一些 sql 查询,以仅查询所需的数据:

当编写普通的 linq 时,它看起来像这样:

var data = dbContext
.TableOne
.Select(t1 => new TableOneSelect
{
    TableOneId = t1.TableOneId,
    TableOneTableTwoReference = new[] { TableOne.FirstTableTwoReference.Invoke(t1) }
        .Select(t2 => new TableTwoSelect
        {
            TableTowId = (Guid?)t2.TableTwoId,
            // ... some more properties of t2
        }).FirstOrDefault(),
    // ... some more properties of t1
});

TableOne.FirstTableTwoReference.Invoke(t1) 已定义

public static Expression<Func<TableOne, TableTwo>> FirstTableTwoReference => (t1) => t1.TableTwoReferences.FirstOrDefault();

目前我有以下动态构建 TableOne 部分:

public Expression<Func<TableOne, TableOneSelect>> Init(TableOneConfig cfg)
{
    var memberBindings = new List<MemberBinding>();
    var selectType = typeof(TableOneSelect);
    var newExpression = Expression.New(selectType);
    var theEntity = Expression.Parameter(typeof(TableOne), "t1");

    // decide if the property is needed and add to the object-initializer
    if (cfg.Select("TableOneId"))
        memberBindings.Add(Expression.Bind(selectType.GetProperty("TableOneId"), Expression.Property(theEntity, nameof("TableOneId"))));

    // ... check other properties of TableOneSelect depending on given config

    var memberInit = Expression.MemberInit(newExpression, memberBindings);
    return Expression.Lambda<Func<tblTournament, EventResourceSelect>>(memberInit, theEntity);
}

TableTwo 相同(不同的属性和不同的数据库表)。

这个我可以像这样动态调用

dbContext.TableOne.Select(t => TableOneHelper.Init(cfg).Invoke(t1));

Invoke 是来自LinqKit 的那个。

但我被TableOneTableTwoReference 的内部部分卡住了,我需要在其中进行枚举以调用TableTwoHelperInit,但我不明白如何实现这一点。

我猜Expression.NewArrayInit(typeof(TableTwo), ...) 将是第一步。但是我仍然被困在如何将t1.TableTwoReferences.FirstOrDefault() 传递给这个调用Select 的数组。

【问题讨论】:

  • 记住x.FirstOrDefault() 等同于Enumerable.FirstOrDefault(x) 可能对您有所帮助。然后你可以使用Expression.Call()
  • @vyrp 我真正的问题更复杂,因为第一个FirstOrDefault() 也是Expression&lt;Func&lt;TableOne, TableTwo&gt;&gt;,我需要调用它来填充数组。将更新我的问题

标签: c# linq expression expression-trees


【解决方案1】:

我猜Expression.NewArrayInit(typeof(TableTwo), ...) 将是第一步。但是我仍然被困在如何将t1.TableTwoReferences.FirstOrDefault() 传递给这个调用Select 的数组。

据我了解,问题是表达等价于什么

new[] { TableOne.FirstTableTwoReference.Invoke(t1) }

这真的很简单。正如您正确陈述的那样,您需要Expression.NewArrayInit 表达式。但是,由于它需要 params Expression[] initializers,而不是 LINQKit Invoke 扩展方法,您应该使用 Expression.Invoke 方法来调用带有外部 theEntity ("t1") 参数的 TableOne.FirstTableTwoReference lambda 表达式:

var t2Array = Expression.NewArrayInit(
    typeof(TableTwo),
    Expression.Invoke(TableOne.FirstTableTwoReference, theEntity));

发出Select 表达式的相同方式:

var t2Selector = TableTwoHelper.Init(cfg2);
// t2Selector is Expression<Func<TableTwo, TableTwoSelect>>
var t2Select = Expression.Call(
    typeof(Enumerable), "Select", new[] { t2Selector.Parameters[0].Type, t2Selector.Body.Type },
    t2Array, t2Selector);

然后FirstOrDefault打电话:

var t2FirstOrDefault = Expression.Call(
    typeof(Enumerable), "FirstOrDefault", new[] { t2Selector.Body.Type },
    t2Select);

最后是外部成员绑定:

memberBindings.Add(Expression.Bind(
    selectType.GetProperty("TableOneTableTwoReference"),
    t2FirstOrDefault));

这将产生相当于您的“普通 linq”方法。

【讨论】:

  • 出于兴趣:您正在写Expression.Call(typeof(Enumerable), "Select",...,但这根本不起作用。总是收到No generic method 'Select' on type 'System.Linq.Enumerable' is compatible with the supplied type arguments and arguments. 最好的解决方法是什么?
  • @KingKerosin 不确定您为什么会收到该错误。我在发布之前已经对其进行了测试,它确实有效。 t2Selector.Parameters[0].TypeTableTwot2Selector.Body.TypeTableTwoSelect,所以我们有 Enumerable.Select&lt;TableTwo, TableTwoSelect&gt;(TableTwo[] array, Func&lt;TableTwo, TableTwoSelect&gt; t2Selector) 这是正确的调用。
【解决方案2】:

添加成员绑定...

memberBindings.Add(Expression.Bind(selectType.GetProperty("TableOneTableTwoReference"), BuildTableTwoExpression(theEntity)));

...然后构建TableTwo的表达式

private Expression BuildTableTwoExpression(ParameterExpression t1)
{
    var arrayEx = Expression.NewArrayInit(typeof(TableTwo), Expression.Invoke(TableOne.FirstTableTwoReference, t1));

    Expression<Func<TableTwo, TableTwoSelect>> selector = (t2 => new TableTwoSelect
    {
        TableTowId = (Guid?)t2.TableTwoId,
        // ... some more properties of t2
    });

    Expression<Func<IEnumerable<TableTwo>, TableTwoSelect>> selectEx =
        ((t1s) => Enumerable.Select(t1s, selector.Compile()).FirstOrDefault());

    return Expression.Invoke(selectEx, arrayEx);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-11
    相关资源
    最近更新 更多