【问题标题】:Append to an expression附加到表达式
【发布时间】:2010-02-09 18:06:59
【问题描述】:

我关注了这个帖子:link text

杰森举个例子:

public static Expression<TDelegate> AndAlso<TDelegate>(this Expression<TDelegate> left, Expression<TDelegate> right)
{
    return Expression.Lambda<TDelegate>(Expression.AndAlso(left, right), left.Parameters);
}

及其用法:

Expression<Func<Client, bool>> clientWhere = c => true;
if (filterByClientFName)
{
    clientWhere = clientWhere.AndAlso(c => c.ClientFName == searchForClientFName);
}
if (filterByClientLName)
{
    clientWhere = clientWhere.AndAlso(c => c.ClientLName == searchForClientLName);
}

我有一个订单表,我按照上面的示例更改了列名,我得到了与帖子创建者类似的错误

没有为类型“System.Func2[Models.Order,System.Boolean]' and 'System.Func2[Models.Order,System.Boolean]”定义二元运算符 AndAlso。

有人对我缺少什么有任何想法吗?

更新:

Eric,我进一步关注了上一篇用户的提问,这里是link text

用户有这个

Expression<Func<Client, bool>> clientWhere = c => true;
Expression<Func<Order, bool>> orderWhere = o => true;
Expression<Func<Product, bool>> productWhere = p => true;

if (filterByClient)
{
    clientWhere = c => c.ClientID == searchForClientID;
}

现在,如果他在filterByClient 中有各种条件,比如他有clientid 和/或其他列名,那么如何构建clientWhere 表达式?

【问题讨论】:

    标签: c# linq


    【解决方案1】:

    您正在尝试构建一个表示此内容的表达式树:

    c => true && c.ClientFName == searchForClientFName
    

    你实际上是在构建一个表示这个的表达式树:

    c => c=> true && c => c.ClientFName == searchForClientFName
    

    这毫无意义。

    现在,您可能天真地认为这会起作用:

    public static Expression<TDelegate> AndAlso<TDelegate>(this Expression<TDelegate> left, Expression<TDelegate> right) 
    { 
    // NOTICE: Combining BODIES:
        return Expression.Lambda<TDelegate>(Expression.AndAlso(left.Body, right.Body), left.Parameters); 
    } 
    

    这会在你的情况下产生一些代表

    c => true && c.ClientFName == searchForClientFName
    

    看起来不错。但实际上这是脆弱的。假设你有

    ... d => d.City == "London" ...
    ... c => c.ClientName == "Fred Smith" ...
    

    并且您使用此方法将它们组合在一起。你会得到一个代表

    的对象
    c => d.City == "London" && c.ClientName == "Fred Smith"
    

    他在里面做什么?

    此外,参数按对象标识进行匹配,而不是按参数名称。如果你这样做

    ... c => c.City == "London" ...
    ... c => c.ClientName == "Fred Smith" ...
    

    并将它们组合成

    c => c.City == "London" && c.ClientName == "Fred Smith"
    

    你们在同一条船上; "c.City" 中的 "c" 是 与其他两个不同的 c

    你真正需要做的是创建一个第三个​​参数对象,替换两个 lambdas 的函数体中每次出现的参数,然后建立一个新的 lambda 表达式树来自生成的替换体。

    您可以通过编写一个遍历表达式树体的访问者来构建替换引擎,并在执行过程中对其进行重写。

    【讨论】:

    • 对于为什么存在问题的问题,这听起来像是一个聪明的答案,但是您还没有真正给出解决方案,以便人们可以从您的帖子中受益......
    • @Michael:那我邀请你写一个你喜欢的答案。
    • 我做了一些类似于 Eric 在这里建议的事情:stackoverflow.com/questions/14248674/… 你可能会发现它很有用。
    • 如果您实际上包含替换引擎代码而不是告诉我们答案是编写替换引擎,那将是一个完美的答案......
    • @ShawndeWet:我鼓励你这样做,然后自己发布一个完美的答案。
    【解决方案2】:

    我很难理解 hvd 的 answer,所以我创建了一些代码来以不同的方式解释它。 hvd 应该得到推荐 ExpressionVisitor 的功劳。我只是无法理解我正在使用的 Linq to X 类型输入函数上下文中的示例。

    我希望这有助于其他人从这个角度来解决这个问题。

    另外,我将组合代码创建为扩展方法,使其更易于使用。


    using System;
    using System.Collections.Generic;
    using System.Linq.Expressions;
    
    namespace ConsoleApplication3
    {
    
        class Program
        {
    
            static void Main(string[] args)
            {
    
                var combined = TryCombiningExpressions(c => c.FirstName == "Dog", c => c.LastName == "Boy");
    
                Console.WriteLine("Dog Boy should be true: {0}", combined(new FullName { FirstName = "Dog", LastName = "Boy" }));
                Console.WriteLine("Cat Boy should be false: {0}", combined(new FullName { FirstName = "Cat", LastName = "Boy" }));
    
                Console.ReadLine();
            }
    
            public class FullName
            {
                public string FirstName { get; set; }
                public string LastName { get; set; }
            }
    
            public static Func<FullName, bool> TryCombiningExpressions(Expression<Func<FullName, bool>> func1, Expression<Func<FullName, bool>> func2)
            {
                return func1.CombineWithAndAlso(func2).Compile();
            }
        }
    
        public static class CombineExpressions
        {
            public static Expression<Func<TInput, bool>> CombineWithAndAlso<TInput>(this Expression<Func<TInput, bool>> func1, Expression<Func<TInput, bool>> func2)
            {
                return Expression.Lambda<Func<TInput, bool>>(
                    Expression.AndAlso(
                        func1.Body, new ExpressionParameterReplacer(func2.Parameters, func1.Parameters).Visit(func2.Body)),
                    func1.Parameters);
            }
    
            public static Expression<Func<TInput, bool>> CombineWithOrElse<TInput>(this Expression<Func<TInput, bool>> func1, Expression<Func<TInput, bool>> func2)
            {
                return Expression.Lambda<Func<TInput, bool>>(
                    Expression.AndAlso(
                        func1.Body, new ExpressionParameterReplacer(func2.Parameters, func1.Parameters).Visit(func2.Body)),
                    func1.Parameters);
            }
    
            private class ExpressionParameterReplacer : ExpressionVisitor
            {
                public ExpressionParameterReplacer(IList<ParameterExpression> fromParameters, IList<ParameterExpression> toParameters)
                {
                    ParameterReplacements = new Dictionary<ParameterExpression, ParameterExpression>();
                    for (int i = 0; i != fromParameters.Count && i != toParameters.Count; i++)
                        ParameterReplacements.Add(fromParameters[i], toParameters[i]);
                }
    
                private IDictionary<ParameterExpression, ParameterExpression> ParameterReplacements { get; set; }
    
                protected override Expression VisitParameter(ParameterExpression node)
                {
                    ParameterExpression replacement;
                    if (ParameterReplacements.TryGetValue(node, out replacement))
                        node = replacement;
                    return base.VisitParameter(node);
                }
            }
        }
    }
    

    【讨论】:

    • 我认为CombineWithAndAlsoCombineWithOrElse 之间没有区别。 CombineWithOrElse中不应该是Expresseion.OrElse(..)吗?
    【解决方案3】:

    如果您需要它,我创建了一个小型 fluent 库来动态创建 lambda 函数,而无需直接处理 System.Linq.Expressions。它可以轻松处理这种情况。举个例子:

    static void Main(string[] args)
    {
      var firstNameCompare = ExpressionUtil.GetComparer<FullName>((a) => a.FirstName);
      var lastNameCompare = ExpressionUtil.GetComparer<FullName>((a) => a.LastName);
    
      Func<FullName, bool> combined = (a) => firstNameCompare(a, "Dog") && lastNameCompare(a, "Boy");
    
      var toCheck = new FullName {FirstName = "Dog", LastName = "Boy"};
      Console.WriteLine("Dog Boy should be true: {0}", combined(toCheck));
      toCheck = new FullName {FirstName = "Cat", LastName = "Boy"};
      Console.WriteLine("Cat Boy should be false: {0}", combined(toCheck));
    
      Console.ReadLine();
    }
    

    GetComparer 方法寻找作为表达式传递的属性并找到 ho 以获取其值,然后构建一个新的表达式来处理比较。

    最后这两个函数被调用“组合”函数进行评估。

    如果您需要更多验证,您可以使用数组并在“组合 lambda”中对其进行迭代

    该库的代码和文档在这里:Kendar Expression Builder 虽然 nuget 包在这里:Nuget Expression Builder

    【讨论】:

      【解决方案4】:

      我试图实现这种东西。我花了一天时间才知道。 我的解决方案基于基于谓词数组的循环中的过滤器。 注意,它完全是通用的和基于反射的,因为关于类和字段的唯一信息是字符串。 为简单起见,我直接调用模型类,但在项目中,您应该由调用模型的控制器进行。

      所以我们开始: 模型部分,其中 T 是类中的泛型

          public class DALXmlRepository<T> where T : class
          {
          public T GetItem(Array predicate)
          {
              IQueryable<T> QueryList = null;
      
              QueryList = ObjectList.AsQueryable<T>().Where((Expression<Func<T, bool>>)predicate.GetValue(0));
              for (int i = 1; i < predicate.GetLength(0); i++)
              {
                  QueryList = QueryList.Where((Expression<Func<T, bool>>)predicate.GetValue(i));
              }
      
              if (QueryList.FirstOrDefault() == null)
                  throw new InvalidOperationException(this.GetType().GetGenericArguments().First().Name + " not found.");
              return QueryList.FirstOrDefault();
          }
          }
      

      现在是 LambdaExpression Builder,它是一个基础(字符串类型或其他),您可以通过更多功能对其进行改进:

          private static Expression BuildLambdaExpression(Type GenericArgument, string FieldName, string FieldValue)
          {
              LambdaExpression lambda = null;
      
              Expression Criteria = null;
      
              Random r = new Random();
              ParameterExpression predParam = Expression.Parameter(GenericArgument, r.Next().ToString());
      
              if (GenericArgument.GetProperty(FieldName).PropertyType == typeof(string))
              {
                  Expression left = Expression.PropertyOrField(predParam, FieldName);
                  Expression LefttoUpper = Expression.Call(left, "ToUpper", null, null);
                  //Type du champ recherché
                  Type propType = GenericArgument.GetProperty(FieldName).PropertyType;
                  Expression right = Expression.Constant(FieldValue, propType);
                  Expression RighttoUpper = Expression.Call(right, "ToUpper", null, null);
                  Criteria = Expression.Equal(LefttoUpper, RighttoUpper);
              }
              else
              {
                  Expression left = Expression.PropertyOrField(predParam, FieldName);
                  Type propType = GenericArgument.GetProperty(FieldName).PropertyType;
                  Expression right = Expression.Constant(Convert.ChangeType(FieldValue, propType), propType);
      
                  Criteria = Expression.Equal(left, right);
              }
      
              lambda = Expression.Lambda(Criteria, predParam);
              return lambda;
          }
      

      现在调用函数:

          public static Hashtable GetItemWithFilter(string Entity, XMLContext contextXML, Hashtable FieldsNameToGet, Hashtable FieldFilter)
          {
              //Get the type
              Type type = Type.GetType("JP.Model.BO." + Entity + ", JPModel");
              Type CtrlCommonType = typeof(CtrlCommon<>).MakeGenericType( type );
              //Making an instance DALXmlRepository<xxx> XMLInstance = new DALXmlRepository<xxx>(contextXML);
              ConstructorInfo ci = CtrlCommonType.GetConstructor(new Type[] { typeof(XMLContext), typeof(String) });
              IControleur DalInstance = (IControleur)ci.Invoke(new object[] { contextXML, null });
      
              //Building the string type Expression<func<T,bool>> to init the array
              Type FuncType = typeof(Func<,>).MakeGenericType( type ,typeof(bool));
              Type ExpressType = typeof(Expression<>).MakeGenericType(FuncType);
              Array lambda = Array.CreateInstance(ExpressType,FieldFilter.Count);
      
              MethodInfo method = DalInstance.GetType().GetMethod("GetItem", new Type[] { lambda.GetType() });
      
              if (method == null)
                  throw new InvalidOperationException("GetItem(Array) doesn't exist for " + DalInstance.GetType().GetGenericArguments().First().Name);
      
              int j = 0;
              IDictionaryEnumerator criterias = FieldFilter.GetEnumerator();
              criterias.Reset();
              while (criterias.MoveNext())
              {
                  if (!String.IsNullOrEmpty(criterias.Key.ToString()))
                  {
                      lambda.SetValue(BuildLambdaExpression(type, criterias.Key.ToString(), criterias.Value.ToString()),j);
                  }
                  else
                  {
                      throw new JPException(JPException.MessageKey.CONTROLER_PARAMFIELD_EMPTY, "GetItemWithFilter", criterias.Key.ToString());
                  }
                  j++;
              }
      
              Object item = method.Invoke(DalInstance, new object[] { lambda });
              }
      

      参数是: String Entity:实体类名。 XMLContext :它是存储库的工作单元,我用来初始化模型类的参数 Hashtable FieldsNameToGet :我要返回的字段列表的索引/值 Hashtable FieldFilter : 用于制作 Lambda 表达式的带有 FieldName/Content 的键/值

      祝你好运。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-09-03
        • 2017-10-12
        • 1970-01-01
        • 1970-01-01
        • 2012-05-23
        • 1970-01-01
        • 1970-01-01
        • 2021-04-25
        相关资源
        最近更新 更多