【问题标题】:How can I generate chained method expressions for LINQ and Entity Framework at runtime?如何在运行时为 LINQ 和实体框架生成链式方法表达式?
【发布时间】:2014-01-06 16:41:41
【问题描述】:

我正在开发一个在运行时生成“where”表达式的小型库。我已经能够使用 Expression.EqualExpression.NotEqual 甚至是字符串上的 .Contains() 方法等不同的运算符来导航对象属性和查询。

我遇到了一种情况,我需要创建一个表示链式方法的表达式,例如:x => x.SomeColumn.Trim().EndsWith("SomeText")。我不知道从哪里开始。

我已经像这样实现了.EndsWith() 方法:

static Expression<Func<TEntity, bool>> GetEndsWithExpression(
    ParameterExpression parameterExpression,
    Expression propertyExpression,
    Expression valueToFind)
{
    var propertyExp = propertyExpression;
    var method = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });
    var someValue = valueToFind;
    var containsMethodExp = Expression.Call(propertyExp, method, someValue);

    return Expression.Lambda<Func<TEntity, bool>>(containsMethodExp, parameterExpression);
}

我想知道您能否帮我弄清楚如何添加.Trim() 方法并将其与.EndsWith() 方法链接起来。

其他一些信息,我已经在我的项目中使用 LINQKit,所以像 .AsExpandable() 这样的东西对我来说有点熟悉。

我最初的(错误)方法(更新)

我认为解决方案应该是这样的:

static Expression<Func<TEntity, bool>> GetTrimEndsWithExpression(
    ParameterExpression parameterExpression,
    Expression propertyExpression,
    Expression valueToFind)
{
    var propertyExp = propertyExpression;

    var trimMethod = typeof(string).GetMethod("Trim");
    var endsWithMethod = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });

    var trimMethodExpression = Expression.Call(propertyExp, trimMethod).Expand();
    var containsMethodExp = Expression.Call(trimMethodExpression, endsWithMethod, valueToFind);

    return Expression.Lambda<Func<TEntity, bool>>(containsMethodExp, parameterExpression);
}

但是,编译失败。它抛出一个错误:

System.Reflection.AmbiguousMatchException: Ambiguous match found.

如何在运行时生成的表达式中链接这两个方法?

【问题讨论】:

  • 我的初始方法不正确。我正在编辑我的问题以显示我的实际方法和实际错误。
  • msdn你需要指定你想要的Trim
  • 我不知道 MSDN 有俄语版 :-)

标签: c# lambda expression


【解决方案1】:

处理这个问题的简单方法是编写一个Compose 方法,它允许您在另一个表达式中组合一个表达式,这在一般情况下解决了这个问题:

public static Expression<Func<T, TResult>> Compose<T, TIntermediate, TResult>(
    this Expression<Func<T, TIntermediate>> first,
    Expression<Func<TIntermedaite, TResult>> second)
{
    return Expression.Lambda<Func<T, TResult>>(
        second.Body.Replace(second.Parameters[0], first.Body),
        first.Parameters[0]);
}

它使用Replace 方法将一个表达式的所有实例替换为另一个表达式,定义如下:

public class ReplaceVisitor:ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, to)
    {
        this.from = from;
        this.to = to;
    }

    public override Expression Visit(Expression ex)
    {
        if(ex == from) to;
        else return base.Visit(ex);
    }  
}

public static Expression Replace(this Expression ex,
    Expression from,
    Expression to)
{
    return new ReplaceVisitor(from, to).Visit(ex);
}

现在我们有了它,我们可以编写我们想要的表达式:

public static Expression<Func<TEntity, bool>> EndsWith<TEntity>(
    public Expression<Func<TEntity, string>> propertySelector,
    string endsWith)
{
    return propertySelector.Compose(str => str.Trim().EndsWith(endsWith));
}

【讨论】:

  • 感谢您的回答。非常详细和有用的信息。
【解决方案2】:

这行抛出了“Ambiguous match found”异常:

 var trimMethod = typeof(string).GetMethod("Trim");

改成:

var trimMethod = typeof(string).GetMethod("Trim", new Type[0]);

【讨论】:

  • 太棒了!这在我的初始测试中有效......在我标记为答案之前,为了理智起见,我将再编写一些测试,以确保它按我认为的方式工作。
  • @quakkels 查看更多带有GetMethod 的样本,可能您想要修剪特定符号
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多