【问题标题】:How do I nest a call to a method within a call to a method using Expression.Call如何在使用 Expression.Call 的方法调用中嵌套对方法的调用
【发布时间】:2011-08-25 16:31:33
【问题描述】:

我试图在调用 contains 之前将 DateTime 转换为字符串。然而,尽管我努力将一个表达式的结果放入另一个表达式中,但我还是失败了。

代码源自对这个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)”方法,并且该方法无法转换为存储表达式。

【问题讨论】:

    标签: c# linq reflection


    【解决方案1】:

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

    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”值是为了表明它是一个非泛型方法调用。

    【讨论】:

    • 这看起来像是答案,但在我标记它之前,您能否澄清一下 new[] { Expression.Constant("D") } 中的 D 是什么。我假设这是 DateTime 对象的格式。其次,参数 new[] { Expression.Constant("D") } 可以为 null、空还是丢失?或者 Expression.Constant("D") 可以是 Expression.Constant("") 还是 Expression.Constant(null) ?
    • @Neil:这是“长日期时间”格式字符串。我假设你想用长格式格式化DateTime,给定方法调用名称。是的,它可能是一个空数组或(可能)null - 不确定后者。使用 Expression.Constant("") 或 Expression.Constant(null) 就像调用 Foo.ToString("")Foo.ToString(null)。 (您可能需要告诉 Expression.Constant 您希望表达式也具有什么类型。)
    • 不,仍然无法正常工作,请查看我现在更完整的修订答案。
    • @Neil:啊 - 你没有说这是针对 LINQ to Entities 的。如果您需要那里的长日期字符串,您是否尝试过调用 ToLongDateString 而不是ToString("D")?在构建自己的表达式树之前,您是否尝试过使用 lambda 表达式和硬编码值来实现这一点?首先让它在 LINQPad 中工作,然后在代码中重新创建它。
    • 这是一个很好的答案,我要感谢您的努力。今天再次处理这个问题后,我再也没有让 LINQ + BLL + EF 一起工作。 Microsoft 需要让基本的 ToString 方法在 EF 中工作(隐式?),就像他们对 Contains 所做的那样。
    【解决方案2】:

    试试这个代码.....

    调用 ToExpression(...)... 前任。 ToExpression(null,Product.Name,"==","Test"); 这里。 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);
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-11-02
      • 2015-09-15
      • 2013-01-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多