【问题标题】:Combining two Linq Expressions组合两个 Linq 表达式
【发布时间】:2012-11-22 12:50:44
【问题描述】:

使用来自各种 SO 帖子的信息,尤其是 blog(更正为使用 AndAlso 而不是 And)我已经设法将类似类型的 linq 表达式组合成一个谓词。但现在我想组合两个表达式,其中一个是另一个的输入。这是完全扩展的原始Expression

    private Expression<Func<T, bool>> ExpressionIsNamed(IEnumerable<EntityName> AccessorNames)
    {
        // works
        Expression<Func<T, bool>> Texpr = x => x.Security.Readers.Any(n => AccessorNames.ToStringArray().Contains(n.Text));

        return Texpr;
    }

请注意,至关重要的是,我需要将这些作为 Expressions 进行管理,因为我的 DB 驱动程序需要遍历树并转换为本机调用,因此使用 Compile() 进行组合不是一个选项。

所以下面是我想与上面的Any() 调用结合的函数。最终输出的表达式必须是 Expression&lt;Func&lt;T, bool&gt;&gt; 类型,我需要将 x.Security.Readers 传递给这个类型。

    public static Expression<Func<IEnumerable<EntityName>,bool>> AccessCheckExpression(IEnumerable<EntityName> AccessorNames)
    {
        return accessList => accessList.Any(n => AccessorNames.ToStringArray().Contains(n.Text));
    }

我已经做到了这一点,但我正在努力研究如何从 accessCheck 换出 accessList =&gt; 并让它在单个表达式中使用 accessList。到目前为止,我有这个;

    private Expression<Func<T, bool>> ExpressionIsNamed(IEnumerable<EntityName> AccessorNames)
    {
        Expression<Func<T, IEnumerable<EntityName>>> accessList = (T x) => x.Security.Readers;
        Expression<Func<IEnumerable<EntityName>, bool>> accessCheck = SecurityDescriptor.AccessCheckExpression(AccessorNames);

        // Combine?
        Expression<Func<T, bool>> Texpr = ??? accessCheck + accessList ???

        return Texpr;
    }

[更新]

所以我还有一点;

    class ParameterUpdateVisitor : System.Linq.Expressions.ExpressionVisitor
    {
        private ParameterExpression _oldParameter;
        private ParameterExpression _newParameter;

        public ParameterUpdateVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
        {
            _oldParameter = oldParameter;
            _newParameter = newParameter;
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            if (object.ReferenceEquals(node, _oldParameter))
                return _newParameter;

            return base.VisitParameter(node);
        }
    }

    static Expression<Func<T, bool>> UpdateParameter<T>(
        Expression<Func<T, IEnumerable<EntityName>>> expr,
        ParameterExpression newParameter)
    {
        var visitor = new ParameterUpdateVisitor(expr.Parameters[0], newParameter);
        var body = visitor.Visit(expr.Body);

        return Expression.Lambda<Func<T, bool>>(body, newParameter);
    }

然后我可以编译;

UpdateParameter(accessList, accessCheck.Parameters[0]);

感谢所有Invoke() 的建议,但我的猜测是,当他们使用 MongoDB 驱动程序时,它不会喜欢InvocationExpression。但是,Invoke 和我上面的代码现在都以完全相同的方式失败。即;

System.ArgumentException: Expression of type 
'System.Func`2[MyLib.Project,System.Collections.Generic.IEnumerable`1[MyLib.EntityName]]' 
cannot be used for parameter of type 
                            'System.Collections.Generic.IEnumerable`1[MyLib.EntityName]'

因此,隐式参数 x.Security.Readers 似乎与普通的旧 IEnumerable&lt;EntityName&gt; 类型不同

【问题讨论】:

  • 你错过了ExpressionIsNamed定义中的类型参数。
  • @HamletHakobyan:不一定。它们的泛型参数可以在包含此私有方法的类上定义。
  • @DanielHilgarth 好的。谢谢!

标签: c# linq


【解决方案1】:

VisitorExpression 是你的朋友。这是合并类似内容的简化但完整的示例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

class Source {
    public List<Value> Values {get;set;}
}
class Value {
    public int FinalValue {get;set;}
}
static class Program {
    static void Main() {
        Expression<Func<Source, IEnumerable<Value>>> f1 =
            source => source.Values;
        Expression<Func<IEnumerable<Value>, bool>> f2 =
            vals => vals.Any(v => v.FinalValue == 3);

        // change the p0 from f2 => f1
        var body = SwapVisitor.Swap(f2.Body, f2.Parameters[0], f1.Body);
        var lambda = Expression.Lambda<Func<Source, bool>>(body,f1.Parameters);
        // which is:
        // source => source.Values.Any(v => (v.FinalValue == 3))
    }

}
class SwapVisitor : ExpressionVisitor {
    private readonly Expression from, to;
    private SwapVisitor(Expression from, Expression to) {
        this.from = from;
        this.to = to;
    }
    public static Expression Swap(Expression body,
        Expression from, Expression to)
    {
        return new SwapVisitor(from, to).Visit(body);
    }
    public override Expression Visit(Expression node)
    {
         return node == from ? to : base.Visit(node);
    }
}

编辑:在你的情况下,这将是:

private Expression<Func<T, bool>> ExpressionIsNamed(
    IEnumerable<EntityName> AccessorNames)
{
    Expression<Func<T, IEnumerable<EntityName>>> accessList =
        (T x) => x.Security.Readers;
    Expression<Func<IEnumerable<EntityName>, bool>> accessCheck =
        SecurityDescriptor.AccessCheckExpression(AccessorNames);


    var body = SwapVisitor.Swap(accessCheck.Body,
        accessCheck.Parameters[0], accessList.Body);
    return Expression.Lambda<Func<T, bool>>(body, accessList.Parameters);
}

【讨论】:

  • 谢谢。我有类似的工作(请参阅我更新的帖子),但我有一个与不匹配的 IEnumerable 类型有关的不同问题。
  • @cirrus 你没有正确合并它们;让我编辑,以在您的代码上下文中显示上述内容...
  • 太棒了,谢谢!不仅编译和运行,它也没有在 DB 驱动程序中出错(这就是我需要组合表达式的原因),而且我有一列通过的测试来证明这一点。
【解决方案2】:

我不确定它是否会起作用,但试试这个:

private Expression<Func<T, bool>> ExpressionIsNamed(IEnumerable<EntityName> AccessorNames)
{
    Expression<Func<T, IEnumerable<EntityName>>> accessList = (x) => x.Security.Readers;
    Expression<Func<IEnumerable<EntityName>, bool>> accessCheck = AccessCheckExpression(AccessorNames);

    var result = Expression.Lambda<Func<T, bool>>(
        Expression.Invoke(accessCheck, accessList.Body), // make invokation of accessCheck, and provide body of accessList (x.Security.Readers) as parameter
        accessList.Parameters.First() // parameter
    );

    return result;
}

【讨论】:

  • 调用不可靠;它不适用于所有提供程序 - EF 臭名昭著地讨厌 Invoke,而且大概仍然如此。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多