【问题标题】:How to Append to an expression如何附加到表达式
【发布时间】:2009-08-12 14:57:56
【问题描述】:

基于my question from yesterday:

如果我必须附加到现有的“where”表达式,我将如何附加?

Expression<Func<Client, bool>> clientWhere = c => true;

if (filterByClientFName)
{
    clientWhere = c => c.ClientFName == searchForClientFName;
}

 if (filterByClientLName)
    {
        clientWhere = c => c.ClientLName == searchForClientLName;
    }

用户可以输入名字或姓氏或两者。如果他们同时输入我想附加到表达式。试图看看是否有一个相当于我可以做的追加

clientWhere.Append or clientWhere += add new expression

或类似的东西

【问题讨论】:

  • 我一直在尝试寻找类似的解决方案,因为我们使用的是我们团队开发的旧 ORML 工具,它支持通过“AND”或“OR”进行附加操作,并且我们的代码在很大程度上依赖于这种 where 扩展.到目前为止,我们还不能切换到 linq,但基本上 linq 会创建 IExpression ,如果您找到自己创建 IExpression 树的方法,那会有所帮助。

标签: c# .net linq


【解决方案1】:

我相信你可以做到以下几点:

Expression<Func<Client, bool>> clientWhere = c => true;

if (filterByClientFName)
{
    var prefix = clientWhere.Compile();
    clientWhere = c => prefix(c) && c.ClientFName == searchForClientFName;
}
if (filterByClientLName)
{
    var prefix = clientWhere.Compile();
    clientWhere = c => prefix(c) && c.ClientLName == searchForClientLName;
}

如果您需要将所有内容保留在Expression-land(与IQueryable 一起使用),您还可以执行以下操作:

Expression<Func<Client, bool>> clientWhere = c => true;

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

通过定义这个扩展方法可以减少冗长:

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);
}

【讨论】:

  • Jason:学习大家提供的所有信息。我尝试了你的方法,通过在静态类中定义扩展,我得到了一个错误:二进制运算符 AndAlso 没有为类型'System.Func2[Models.Client,System.Boolean]' and 'System.Func2[Models.Client,System.Boolean]'定义。
  • 即使您修复了扩展方法中的错误,这也是一种非常脆弱的方法。详情请参阅我对stackoverflow.com/questions/2231302/append-to-an-expression-c 的回复。
  • @Jason:我认为显示名称为“Jason”的用户太多。
【解决方案2】:

这是一个复杂的场景。您几乎是在 LINQ 之上构建自己的查询引擎。如果您想在所有条件之间进行逻辑与,JaredPar 的解决方案(它去了哪里?)非常棒,但情况可能并非总是如此。

当我最近在我的一个项目中为此争论不休时,我创建了两个列表:

List<Predicate<T>> andCriteria;
List<Predicate<T>> orCriteria;

(在这种情况下,T 是 Client,对你来说)

我会用我希望为真的谓词填充列表。例如,

decimal salRequirement = 50000.00;
andCriteria.Add(c => c.Salary > salRequirement);
orCriteria.Add(c => c.IsMarried);

然后,我将检查 Where 子句中列表中的所有条件。例如:

Expression<Func<Client, bool>> clientWhere =
    c => andCriteria.All(pred => pred(c) ) && orCriteria.Any(pred => pred(c) );

为了便于阅读,也可以使用 for 循环来完成。请记住在应用 OR 和 AND 子句时使用正确的操作顺序。

【讨论】:

  • 请注意,这也考虑到您想要做的不仅仅是使用“==”的情况,因为谓词可以是 any 布尔函数。
  • 乔希:如果我用你的风格构建它,顺便说一句很棒,然后调用查询:var query = from C in db.clients.Where(clientWhere) join O in db.orders。 c.clientid 上的 Where(orderWhere) 等于 O.clientid 在 db.products 中加入 P。O.productid 上的 Where(productWhere) 等于 P.productid 选择新 {C,O};我收到此错误:本地序列不能用于查询运算符的 LINQ to SQL 实现中,但 Contains() 运算符除外。
  • 不确定那个 - 它可能与您使用的特定标准有关。您是否尝试过使用空标准列表?
  • Josh:我的查询返回 IQueryable 然后我将其发送到分页例程: public PaginatedList(IQueryable source, int pageIndex, int pageSize) { PageIndex = pageIndex;页面大小 = 页面大小; TotalCount = source.Count(); //这是失败的地方 TotalPages = (int)Math.Ceiling(TotalCount / (double)PageSize); this.AddRange(source.Skip(PageIndex * PageSize).Take(PageSize)); }
【解决方案3】:

如果您遇到类似的问题,您可以在这个伟大的topic 中找到所有可能的解决方案。 或者只是使用 PredicateBuilder 是这个 poporse 的好帮手。

var predicate = PredicateBuilder.True<Client>();

if (filterByClientFName)
{
    predicate = predicate.And(c => c.ClientFName == searchForClientFName);
}

if (filterByClientLName)
{
        predicate = predicate.And(c => c.ClientLName == searchForClientLName);
}

var result = context.Clients.Where(predicate).ToArray();

这是一些构建器实现。

public static class PredicateBuilder
    {
        // Creates a predicate that evaluates to true.        
        public static Expression<Func<T, bool>> True<T>() { return param => true; }

        // Creates a predicate that evaluates to false.        
        public static Expression<Func<T, bool>> False<T>() { return param => false; }

        // Creates a predicate expression from the specified lambda expression.        
        public static Expression<Func<T, bool>> Create<T>(Expression<Func<T, bool>> predicate) { return predicate; }

        // Combines the first predicate with the second using the logical "and".        
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.AndAlso);
        }

        // Combines the first predicate with the second using the logical "or".        
        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.OrElse);
        }

        // Negates the predicate.        
        public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expression)
        {
            var negated = Expression.Not(expression.Body);
            return Expression.Lambda<Func<T, bool>>(negated, expression.Parameters);
        }

        // Combines the first expression with the second using the specified merge function.        
        static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
        {
            // zip parameters (map from parameters of second to parameters of first)
            var map = first.Parameters
                .Select((f, i) => new { f, s = second.Parameters[i] })
                .ToDictionary(p => p.s, p => p.f);

            // replace parameters in the second lambda expression with the parameters in the first
            var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);

            // create a merged lambda expression with parameters from the first expression
            return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
        }

        class ParameterRebinder : ExpressionVisitor
        {
            readonly Dictionary<ParameterExpression, ParameterExpression> map;

            ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
            {
                this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
            }

            public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
            {
                return new ParameterRebinder(map).Visit(exp);
            }

            protected override Expression VisitParameter(ParameterExpression p)
            {
                ParameterExpression replacement;
                if (map.TryGetValue(p, out replacement))
                {
                    p = replacement;
                }
                return base.VisitParameter(p);
            }
        }
    }

【讨论】:

  • 在 PredicateBuilder 中插入 ParameterRebinder 是不是无意的错误?它们是 2 个不同的类,2 种不同的方法来实现相同的目标。 PredicateBuilder 本身不是 EF 友好的,它需要该扩展库中的一些其他类。另一个似乎是 EF 友好的。感谢您提供源链接,这是一篇很棒的文章!
【解决方案4】:

看看Predicate Builder,我相信这可能对你有用。

【讨论】:

  • 谓词生成器可以解决问题。它与 LinqToSQL 配合得很好,而且最重要的是它的功能很容易使用。
【解决方案5】:

这不完全是您问题的答案,但是,我一直在寻找与您相同的东西,然后我找到了更好的问题答案。

您可以检索 IQueryable,而不是构建动态表达式 然后像这样过滤你想要的:

var customers = CustomerRepository.AllEntities();

if (!forename.IsNullOrEmpty())
    customers = customers.Where(p => p.Forename == forename);
if (!familyname.IsNullOrEmpty())
    customers = customers.Where(p => p.FamilyNames.Any(n => n.Name==familyname));
if (dob.HasValue)
    customers = customers.Where(p => p.DOB == dob);

注意:我担心执行多个“.Where”语句,因为我担心这会在数据库中生成多个查询,或者因为我必须检索所有记录并然后过滤它们,但这不是真的, 只有在调用 .ToList() 方法时,Linq 才会动态生成一个查询。

Here 你可以看到我举的例子的原始问题。

【讨论】:

    【解决方案6】:

    从 2020 年开始,解决方案将变得更加简单和优雅 :)

        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.And);
        }
    

    适用于 IQueryable。

    【讨论】:

    • 您能否提供有关 Compose 方法的任何文档?
    • 可以找到here。我刚刚意识到对于上述 trueboroda 提供的如此简单的任务和重复解决方案似乎过于复杂
    • 这是不完整的答案。 compose 不存在,因为它是在自定义类中创建的方法。而 也不存在。该解决方案具有误导性,并没有提供任何有用的东西。您从自定义类中取出了 1 个方法,并且没有包含它所依赖的任何其他代码。
    • 嗨@HeribertoLugo,我想我承认评论中有重复,请参阅trueboroda 的solution 详细信息。
    【解决方案7】:

    或者添加到 Josh 的东西(把它放在我的技巧包里):

    public static IQueryable<TSource> ObjectFilter<TSource>(this TSource SearchObject, List<Predicate<TSource>> andCriteria, List<Predicate<TSource>> orCriteria) where TSource : IQueryable<TSource>
            {
                //Yeah :)
                Expression<Func<TSource, bool>> ObjectWhere = O => andCriteria.All(pred => pred(O)) && orCriteria.Any(pred => pred(O));
                return SearchObject.Where<TSource>(ObjectWhere);
            }
    

    【讨论】:

      【解决方案8】:

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

      所以我们开始吧: 模型部分,其中 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 });
              }
      

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

      祝你好运。

      【讨论】:

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