在LINQ to Entities表达式中调用Contains()方法,针对的类型不是字符串

4

我正在尝试在一个允许用户创建过滤条件的网格中实现搜索/过滤功能,这些条件涉及到一个列、一个操作和一个值。

例如:列1包含“某些值”。

当所选列包含字符串类型时,以下代码可以正常工作:

case WhereOperation.Contains:
    Expression condition = Expression.Call(
       memberAccess, //memberAccess is a MemberExpression of the property bound to the column.
       typeof(string).GetMethod("Contains"),
       Expression.Constant(value.ToString())); // value is what we are checking to see if the column contains.
    LambdaExpression lambda = Expression.Lambda(condition, parameter);
    break;

然而,当列绑定的属性不是字符串类型(即Int)时,这将失败,因为类型Int没有“Contains”方法。我该如何在调用其上的“Contains”之前首先获取memberAccess的ToString()值?
注1:无法在编译时确定“memberAccess”代表的属性的类型。 注2:Lambda表达式最终被用于LINQ 2实体查询中,该查询不能明确处理ToString()。 (请参见下面我尝试过的内容)
这是我尝试的一个解决方案,但它在LINQ表达式评估时失败,因为LINQ 2实体不支持ToString():
case WhereOperation.Contains:
    Expression stringValue = Expression.Call(memberAccess,
       typeof(object).GetMethod("ToString"));
    Expression condition = Expression.Call(
       stringValue, 
       typeof(string).GetMethod("Contains"),
       Expression.Constant(value.ToString()));
    LambdaExpression lambda = Expression.Lambda(condition, parameter);
    break;
1个回答

7

是的,由于安全原因,在Linq to Entities中无法使用ToString。我记不得在哪里看到了这个信息。但是在Linq2Sql中是受支持的。

不过,你可以使用SqlFunctions.StringConvert。

我为您制作了一个简单的扩展:

public static class ExpressionExtensions
{
    public static Expression<Func<T, bool>> AddContains<T>(this LambdaExpression selector, string value)
    {
        var mi = typeof (string).GetMethods().First(m => m.Name == "Contains" && m.GetParameters().Length == 1);


        var body = selector.GetBody().AsString();
        var x = Expression.Call(body, mi, Expression.Constant(value));

        LambdaExpression e = Expression.Lambda(x, selector.Parameters.ToArray());
        return (Expression<Func<T, bool>>)e;
    }

    public static Expression GetBody(this LambdaExpression expression)
    {
        Expression body;
        if (expression.Body is UnaryExpression)
            body = ((UnaryExpression)expression.Body).Operand;
        else
            body = expression.Body;

        return body;
    }

    public static Expression AsString(this Expression expression)
    {
        if (expression.Type == typeof (string))
            return expression;

        MethodInfo toString = typeof(SqlFunctions).GetMethods().First(m => m.Name == "StringConvert" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == typeof(double?));
        var cast = Expression.Convert(expression, typeof(double?));
        return Expression.Call(toString, cast);
    }
}

使用方法:

        IQueryable<Foo> seed = ...;
        Expression<Func<Foo, int>> selector = x => x.Id;
        var expression = selector.AddContains<Foo>("3");
        seed.Where(expression);  

尝试将此代码适应到我的代码中,以便符合我的使用方式,但我认为传递给AsString()的长度存在问题。它需要是选择器值的长度,而不是我们作为Contains()参数传递的值的长度。我不确定如何获取选择器值的长度。 - Tom
我并没有放弃这个问题。只是有其他的优先事项出现,所以我还没有机会回到这段代码。我应该会在星期二处理它,并根据情况接受答案或进行评论。对于延迟感到抱歉。 - Tom
抱歉耽搁了这么久,但我终于回到了这个问题,并且你的解决方案非常有效。谢谢! - Tom
+1: @maxlego 当一个人开始学习表达式树的时候,所有这些东西似乎都不那么简单了...非常感谢。你的答案确实有帮助。 - horgh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接