【问题标题】:Utilizing Funcs within expressions?在表达式中使用函数?
【发布时间】:2014-05-07 00:05:05
【问题描述】:

背景

我有一个测试通过但在管道中发生错误的示例,我不知道为什么。我想弄清楚发生了什么,但我是表达式构造的新手,不想做任何假设。

这是一种搜索过滤机制。它使用 ServiceStack 的 PredicateBuilder 实现。我基本上有一个我传入的值列表,我希望它构造一个表达式树。我之前只使用Func<T<bool>> 完成了此操作,但意识到我需要使用Expression<Func<T<bool>>> 结束。无赖。

目标

从可重复使用的搜索过滤器类型构建的搜索过滤器,它由Funcs 和Expressions 构建而成,允许我从对象传递字段名称以及我应该匹配并结束的值我们可以运行 Where() 声明的东西。

代码/问题

我正在尝试的通用“可空布尔”过滤器——设置可接受的项目并返回一个用于帮助过滤的函数:

public class NullableBoolFilter : IGenericSearchFilter<bool?>
{
    public Func<bool?, bool> GetFilterFunc(string valuesToProcess)
    {
        var acceptableValues = new List<bool?>();

        if (string.IsNullOrWhiteSpace(valuesToProcess))
        {
            // all values acceptable
            acceptableValues = new List<bool?>{true, false, null};
        }
        else
        {
            if (!valuesToProcess.Contains("0") && !valuesToProcess.Contains("1"))
            {
                throw new ArgumentException("Invalid Nullable boolean filter attribute specified");
            }
            if (valuesToProcess.Contains("0"))
            {
                acceptableValues.Add(false);

            }
            if (valuesToProcess.Contains("1"))
            {
                acceptableValues.Add(true);
            }
        }

        Func<bool?, bool> returnFunc = delegate(bool? item) { return acceptableValues.Any(x=>x == item); };
        return returnFunc;
    }
}

然后我有另一个过滤器,它继承自 NullableBoolFilter 并尝试使用 Func:

public class ClaimsReportIsMDLFilter : NullableBoolFilter, ISearchFilter<vSEARCH_ClaimsReport>
{
    public Expression<Func<vSEARCH_ClaimsReport, bool>> GetExpression(string valuesToProcess)
    {
        var theFunc = base.GetFilterFunc(valuesToProcess);

        Expression<Func<vSEARCH_ClaimsReport, bool>> mdlMatches = item => theFunc(item.IsMDL);

        var predicate = PredicateBuilder.False<vSEARCH_ClaimsReport>();
        predicate = predicate.Or(mdlMatches);

        return predicate;

    }
}

以下测试通过:

public class ClaimsReportIsMDLFilterTests
{
    // ReSharper disable InconsistentNaming
    private readonly vSEARCH_ClaimsReport ItemWithMDL = new vSEARCH_ClaimsReport { IsMDL = true };
    private readonly vSEARCH_ClaimsReport ItemWithoutMDL = new vSEARCH_ClaimsReport { IsMDL = false };
    private readonly vSEARCH_ClaimsReport ItemWithNullMDL = new vSEARCH_ClaimsReport { IsMDL = null };
    // ReSharper restore InconsistentNaming

    [Fact]
    public void WithSearchValueOf1_HidesNonMDLAndNull()
    {

        var sut = this.GetCompiledExpressionForValues("1");

        sut.Invoke(ItemWithMDL).Should().BeTrue();
        sut.Invoke(ItemWithoutMDL).Should().BeFalse();
        sut.Invoke(ItemWithNullMDL).Should().BeFalse();

    }

    private Func<vSEARCH_ClaimsReport, bool> GetCompiledExpressionForValues(string searchValue)
    {
        return new ClaimsReportIsMDLFilter().GetExpression(searchValue).Compile();
    }

}

问题

当我实际尝试运行它时,我收到错误:

从范围“”引用的“vSEARCH_ClaimsReport”类型的变量“参数”,但未定义

这对我来说是有道理的——在评估它时,我没有真正的对象可以传递给Func。但是,我很困惑为什么我的测试可能会通过,但这并没有在实际使用中。

问题

  • 为什么我的测试可能通过但我仍然收到此错误?
  • 我应该如何着手解决这个问题?
  • 有没有一种远程简单的方法可以将Func 转换为Expression,我可以将字段传递到其中?
  • 我是否需要放弃通用过滤器的想法并让每个类根据传入的输入手动将表达式添加到PredicateBuilder?这是可行的,但似乎可以进一步减少工作量。

【问题讨论】:

  • 您的NullableBoolFilter 似乎围绕着委托,而您的ClaimsReportIsMDLFilter 围绕着表达式树。这几乎肯定是问题所在。如果您想使用表达式树,则需要始终如一地使用它们——只要您有一个引用某个本地 Func&lt;&gt; 的表达式树,就无法将其转换为 SQL(或其他) .
  • @JonSkeet 感谢(快速!)响应。这段代码目前正在运行:gist.github.com/SeanKilleen/0a8ffa639c4916af0585 考虑到这一点,有没有什么方法可以创建我可以传入一个可为空的 bool 字段并让它每次都基于相同的过程进行评估的东西?或者,一旦我使用了表达方式,我就会陷入这种困境吗?我有几个类似的过滤器,所以只是想统一处理并尽可能保持 DRY。

标签: c# lambda delegates func ormlite-servicestack


【解决方案1】:

为什么我的测试会通过 [...]

因为您的测试只是将表达式编译成它所代表的代码并调用它。它不需要实际解析表达式树并查看它所代表的代码在做什么,它只是运行它并确保输出正确。

为什么 [...] 我仍然会收到此错误?

因为当您实际使用它时,它不仅仅是执行代码;而是执行代码。相反,它正在查看表达式树以尝试确定代码在做什么以便可以将其转换为其他内容,而不是使其可以作为 C# 代码运行。

您的表达式除了调用委托外什么都不做。遍历表达式树的人无法看到委托内部并知道它在做什么。知道您正在调用另一种方法并不是可以翻译成另一种语言的。

我应该如何开始尝试解决这个问题?

您需要从一开始就生成一个Expression,而不是生成一个Func,然后再创建一个调用它的Expression

有没有一种远程简单的方法来获取该 Func 并将其转换为我可以将字段传递到的表达式?

没有。您需要提取函数的 IL 代码,将其反编译为 C# 代码,然后构建 Expression 对象来表示该代码。这几乎不会发生。


你几乎需要让GetFilterFunc 返回一个Expression,才能让它工作。幸运的是,考虑到你所拥有的,这很容易做到。您只需更改方法签名并将最后两行替换为以下内容:

return item => acceptableValues.Any(x => x == item);

然后瞧。 lambda 可以根据上下文编译成 Expression 对象,而不是委托,因此如果方法的返回类型是 Expression&lt;Func&lt;bool?,bool&gt;&gt;,这就是您将得到的。

现在,在GetExpression 中使用它。首先,PredicateBuilder 并没有真正做任何事情。在表达式中添加 OR FALSE 不会改变任何意义。这一切都可以过去。剩下的就是使用Expression&lt;Func&lt;bool?,bool&gt;&gt; 并通过拉出一个布尔属性将其更改为Expression&lt;Func&lt;vSEARCH_ClaimsReport, bool&gt;&gt;。要做到这一点,表达式的工作量要比委托多一点。我们需要做更多的工作来组合它们,而不是仅仅调用表达式。我们将要编写一个方法来执行此操作:

public static Expression<Func<TFirstParam, TResult>>
    Compose<TFirstParam, TIntermediate, TResult>(
    this Expression<Func<TFirstParam, TIntermediate>> first,
    Expression<Func<TIntermediate, TResult>> second)
{
    var param = Expression.Parameter(typeof(TFirstParam), "param");

    var newFirst = first.Body.Replace(first.Parameters[0], param);
    var newSecond = second.Body.Replace(second.Parameters[0], newFirst);

    return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}

这依赖于使用以下方法将一个表达式的所有实例替换为另一个:

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}

internal class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}

这样做是用第一个表达式的主体替换第二个表达式参数的所有实例,有效地将该表达式内联到第二个。剩下的就是简单地用一个新的单个参数替换所有参数并将其包装回一个 lambda。

既然我们有了,我们的方法就很简单了:

public Expression<Func<vSEARCH_ClaimsReport, bool>> GetExpression(
    string valuesToProcess)
{
    Expression<Func<vSEARCH_ClaimsReport, bool?>> selector = 
        item => item.IsMDL;
    return selector.Compose(base.GetFilterFunc(valuesToProcess));
}

【讨论】:

  • @Servy,非常感谢您的解释。我想我仍然可能会遗漏一些东西。在这个要点中,我尝试实现它:gist.github.com/SeanKilleen/1fa043025fed436eecc3 但是,我收到了同样的错误。我保留了PredicateBuilder,因为我们在更大的And 搜索中进行Or 搜索,这是OrmLite 期望它进入AFAIK 的方式。这可能是导致问题的原因吗?再次感谢您的精彩解释;期待更好地理解这一点。
  • @SeanKilleen 我的猜测是您的查询提供程序需要解决某种配置问题,而我不熟悉您提到的问题。实际的表达式需要清理,这 应该 生成查询提供程序可以使用的东西,但是如果除了构建正确的 Expression 之外还有任何问题,那么我无法为您提供帮助与。
  • @Servy 完全可以理解,谢谢!我只是想确保我没有以某种方式搞砸了实施。我会进一步检查。谢谢!
  • @SeanKilleen 你试过使用 Ormlite 提供的 .And 和 .Or 方法吗?
  • @DanB 我们实际上是在生成我们自己的表达式,以便我们可以标准化它们并在 Ormlite 的 PredicateBuilder And()Or 方法中使用它们。这让我们可以查看传入的列和值,自动查找所有过滤器,并生成可以传递给 Ormlite 的表达式。我们创建个人或过滤器,然后将它们一起创建。我确实得到了这个工作;如果有机会,我会在单独的答案中发布我的解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-01-30
  • 2014-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多