如何使用Expression.Call在对方法的调用中嵌套对方法的调用

时间:2011-08-25 16:31:33

标签: c# linq reflection

我试图在调用contains之前将DateTime转换为String。尽管我努力将一个表达式的结果放到另一个表达式中,但我却悲惨地失败了。

代码源自此question jqgrid的最高答案,其中asp.net webmethod和json使用排序,分页,搜索和LINQ - 但需要动态运算符。

假设我有以下方法作为question中的StringExtension:

public static class StringExtensions
{
  public static MemberExpression ToMemberExpression(this string source, ParameterExpression p)
  {
    if (p == null)
        throw new ArgumentNullException("p");

    string[] properties = source.Split('.');

    Expression expression = p;
    Type type = p.Type;

    foreach (var prop in properties)
    {
        var property = type.GetProperty(prop);
        if (property == null)
            throw new ArgumentException("Invalid expression", "source");

        expression = Expression.MakeMemberAccess(expression, property);
        type = property.PropertyType;
    }

    return (MemberExpression)expression;
  }
}

因此我也从question获得了以下方法,然后我将其改编为DateTime。

public virtual Expression<Func<T, bool>> CreateExpression<T>(string searchField, string searchString, string searchOper)
    {
        Expression exp = null;
        var p = Expression.Parameter(typeof(T), "p");

        Expression propertyAccess = searchField.ToMemberExpression(p);

        switch (searchOper)
        {
            case "bw":
                exp = Expression.Call(propertyAccess, typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) }), Expression.Constant(searchString));
                break;

            // New code by me
            case "cn":

                if (propertyAccess.Type == typeof(DateTime))
                {
                    // My faulty logic - from Jon Skeet answer below

                    Expression toStringCall = Expression.Call(
                        propertyAccess, "ToString",
                        null,
                        new[] { Expression.Constant("D") });

                    Expression containsCall = Expression.Call(
                        toStringCall, "Contains",
                        null,
                        new[] { Expression.Constant(searchString) });

                    exp = containsCall;
                }
                else
                {
                    // Unchanged
                    exp = Expression.Call(propertyAccess, typeof(string).GetMethod("Contains", new Type[] { typeof(string) }), Expression.Constant(searchString));
                }
                break;
            case "ew":
                exp = Expression.Call(propertyAccess, typeof(string).GetMethod("EndsWith", new Type[] { typeof(string) }), Expression.Constant(searchString));
                break;
            case "gt":
                exp = Expression.GreaterThan(propertyAccess, Expression.Constant(searchString, propertyAccess.Type));
                break;
            case "ge":
                exp = Expression.GreaterThanOrEqual(propertyAccess, Expression.Constant(searchString, propertyAccess.Type));
                break;
            case "lt":
                exp = Expression.LessThan(propertyAccess, Expression.Constant(searchString, propertyAccess.Type));
                break;
            case "le":
                exp = Expression.LessThanOrEqual(propertyAccess, Expression.Constant(searchString, propertyAccess.Type));
                break;
            case "eq":
                exp = Expression.Equal(propertyAccess, Expression.Constant(searchString.ToType(propertyAccess.Type), propertyAccess.Type));
                break;
            case "ne":
                exp = Expression.NotEqual(propertyAccess, Expression.Constant(searchString, propertyAccess.Type));
                break;
            default:
                return null;
        }

        return (Expression<Func<T, bool>>)Expression.Lambda(exp, p);
    }

我收到以下异常。

LINQ to Entities无法识别方法'System.String ToString(System.String)'方法,并且此方法无法转换为商店表达式。

2 个答案:

答案 0 :(得分:2)

怀疑你想要这样的东西(从以前的版本修复):

using System;
using System.Linq.Expressions;

public class Person
{
    public DateTime DateOfBirth { get; set; }
}

public class Test
{
    static void Main()
    {
        var expr = Foo<Person>("DateOfBirth", "1976");

        Person p = new Person
        {
            DateOfBirth = new DateTime(1976, 6, 19)
        };

        Console.WriteLine(expr.Compile()(p));
    }

    static Expression<Func<T, bool>> Foo<T>(string propertyName,
                                            string searchValue)
    {
        ParameterExpression parameter = Expression.Parameter(typeof(T), "x");
        Expression property = Expression.Property(parameter, propertyName);
        Expression toStringCall = Expression.Call(
            property, "ToString",
            null,
            new[] { Expression.Constant("D") });

        Expression containsCall = Expression.Call(
            toStringCall, "Contains",
            null,
            new[] { Expression.Constant(searchValue) });

        return Expression.Lambda<Func<T, bool>>(containsCall, parameter);
    }
}

请注意,“null”值表示它是非泛型方法调用。

答案 1 :(得分:0)

试试这段代码......

致电ToExpression(...)...... 防爆。 ToExpression(NULL,Product.Name, “==”, “测试”); 这里。 Product.Name是嵌套属性..

public static Expression<Func<T, bool>> ToExpression<T>(string andOrOperator, string propName, string opr, string value, Expression<Func<T, bool>> expr = null)
    {
        Expression<Func<T, bool>> func = null;
        try
        {
            ParameterExpression paramExpr = Expression.Parameter(typeof(T));
            var arrProp = propName.Split('.').ToList();
            Expression binExpr = null;
            string partName = string.Empty;
            arrProp.ForEach(x =>
            {
                Expression tempExpr = null;
                partName = partName.IsNull() ? x : partName + "." + x;
                if (partName == propName)
                {
                    var member = NestedExprProp(paramExpr, partName);
                    var type = member.Type.Name == "Nullable`1" ? Nullable.GetUnderlyingType(member.Type) : member.Type;
                    tempExpr = ApplyFilter(opr, member, Expression.Convert(ToExprConstant(type, value), member.Type));
                }
                else
                    tempExpr = ApplyFilter("!=", NestedExprProp(paramExpr, partName), Expression.Constant(null));
                if (binExpr != null)
                    binExpr = Expression.AndAlso(binExpr, tempExpr);
                else
                    binExpr = tempExpr;
            });
            Expression<Func<T, bool>> innerExpr = Expression.Lambda<Func<T, bool>>(binExpr, paramExpr);
            if (expr != null)
                innerExpr = (andOrOperator.IsNull() || andOrOperator == "And" || andOrOperator == "AND" || andOrOperator == "&&") ? innerExpr.And(expr) : innerExpr.Or(expr);
            func = innerExpr;
        }
        catch { }
        return func;
    }

    private static MemberExpression NestedExprProp(Expression expr, string propName)
    {
        string[] arrProp = propName.Split('.');
        int arrPropCount = arrProp.Length;
        return (arrPropCount > 1) ? Expression.Property(NestedExprProp(expr, arrProp.Take(arrPropCount - 1).Aggregate((a, i) => a + "." + i)), arrProp[arrPropCount - 1]) : Expression.Property(expr, propName);
    }

    private static Expression ToExprConstant(Type prop, string value)
    {
        if (value.IsNull())
            return Expression.Constant(value);
        object val = null;
        switch (prop.FullName)
        {
            case "System.Guid":
                val = value.ToGuid();
                break;
            default:
                val = Convert.ChangeType(value, Type.GetType(prop.FullName));
                break;
        }
        return Expression.Constant(val);
    }

    private static Expression ApplyFilter(string opr, Expression left, Expression right)
    {
        Expression InnerLambda = null;
        switch (opr)
        {
            case "==":
            case "=":
                InnerLambda = Expression.Equal(left, right);
                break;
            case "<":
                InnerLambda = Expression.LessThan(left, right);
                break;
            case ">":
                InnerLambda = Expression.GreaterThan(left, right);
                break;
            case ">=":
                InnerLambda = Expression.GreaterThanOrEqual(left, right);
                break;
            case "<=":
                InnerLambda = Expression.LessThanOrEqual(left, right);
                break;
            case "!=":
                InnerLambda = Expression.NotEqual(left, right);
                break;
            case "&&":
                InnerLambda = Expression.And(left, right);
                break;
            case "||":
                InnerLambda = Expression.Or(left, right);
                break;
            case "LIKE":
                InnerLambda = Expression.Call(left, typeof(string).GetMethod("Contains", new Type[] { typeof(string) }), right);
                break;
            case "NOTLIKE":
                InnerLambda = Expression.Not(Expression.Call(left, typeof(string).GetMethod("Contains", new Type[] { typeof(string) }), right));
                break;
        }
        return InnerLambda;
    }

    public static Expression<Func<T, object>> PropExpr<T>(string PropName)
    {
        ParameterExpression paramExpr = Expression.Parameter(typeof(T));
        var tempExpr = Extentions.NestedExprProp(paramExpr, PropName);
        return Expression.Lambda<Func<T, object>>(Expression.Convert(Expression.Lambda(tempExpr, paramExpr).Body, typeof(object)), paramExpr);

    }
    public static IQueryOver<T, T> OrderBy<T>(this IQueryOver<T, T> Collection, string sidx, string sord)
    {
        return sord == "asc" ? Collection.OrderBy(NHibernate.Criterion.Projections.Property(sidx)).Asc : Collection.OrderBy(NHibernate.Criterion.Projections.Property(sidx)).Desc;
    }

    public static Expression<Func<T, TResult>> And<T, TResult>(this Expression<Func<T, TResult>> expr1, Expression<Func<T, TResult>> expr2)
    {
        var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
        return Expression.Lambda<Func<T, TResult>>(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);
    }

    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
    {
        var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
        return Expression.Lambda<Func<T, bool>>(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
    }