【问题标题】:Dynamic Linq query Contains List动态 Linq 查询包含列表
【发布时间】:2014-05-19 18:17:42
【问题描述】:

我正在使用动态 Linq 进行通用搜索。 我有 ID 列表:

List<int> idList = new List<int> { 1, 5, 6};

在普通的 Linq 中,我会写:

q = q.Where(a => idList.Contains(a.MyId));

但现在我必须使用System.Linq.Dynamic,因为我事先不知道该列的名称。

string someId = "CustomId";
q = q.Where("@0"+ ".Contains(" + someId + ")", idList.ToArray());

但这会产生错误:

“'Int32' 类型中不存在适用的方法 'Contains'”

我怎样才能做到这一点?

是否有一些扩展库为 dynamic Linq 或其他方式实现 Contains

【问题讨论】:

  • 使用表达式树。
  • 表达式树是下面接受的答案中描述的解决方案。 @pil0t 问题可能是重复的,但答案不是。唯一给定的解决方案需要对库进行更改,而在这里我得到了如何使用 Expression 轻松扩展它的答案。

标签: c# linq dynamic arraylist contains


【解决方案1】:

你可以写这样的东西来动态构建你的查询函数:

public static Func<ObjT, bool> PropertyCheck<ObjT, PropT>(string propertyName, Expression<Func<PropT, bool>> predicate)
{
    var paramExpr = Expression.Parameter(typeof(ObjT));
    var propExpr = Expression.Property(paramExpr, propertyName);
    return Expression.Lambda<Func<ObjT, bool>>(Expression.Invoke(predicate, propExpr), paramExpr).Compile();
}

那么,它可以这样使用:

foos.Where(PropertyCheck<Foo, int>("MyId", x => idList.Contains(x)));

当然,您也可以只提供自己的 Where 扩展方法来一次性完成所有操作:

public static IEnumerable<T> Where<T, PropT>(this IEnumerable<T> self, string propertyName, Expression<Func<PropT, bool>> predicate)
{
    var paramExpr = Expression.Parameter(typeof(T));
    var propExpr = Expression.Property(paramExpr, propertyName);
    return self.Where<T>(Expression.Lambda<Func<T, bool>>(Expression.Invoke(predicate, propExpr), paramExpr).Compile());
}
foos.Where<Foo, int>("MyId", x => idList.Contains(x));

【讨论】:

    【解决方案2】:

    您可以使用expressions 来执行此动态查询,试试这样的示例:

    导入这些命名空间:

    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    

    试试这个:

    // a reference parameter
    var x = Expression.Parameter(typeof (YourType), "x");
    
    // contains method
    var containsMethod = typeof (string).GetMethod("Contains", new[] {typeof (string)});
    
    // reference a field
    var fieldExpression = Expression.Property(instance, "PropertyName");
    
    // your value
    var valueExpression = Expression.Constant(yourId);
    
    // call the contains from a property and apply the value
    var containsValueExpression = Expression.Call(fieldExpression, containsMethod, valueExpression);
    
    // create your final lambda Expression
    var filterLambda = Expression.Lambda<Func<YourType, bool>>(containsValueExpression, x);
    
    // apply on your query
    q = q.Where(finalLambda);
    

    Obs:确保您的属性有一个名为 contains 的方法。

    【讨论】:

      【解决方案3】:

      如果您查看 Dynamic LINQ 的源代码,您会发现在许多情况下解析取决于变量 predefinedTypes

      在您的情况下,您需要像这样更改此变量

      static readonly Type[] predefinedTypes = {
          ....
          ,typeof(List<int>)
      };
      

      之后下一个代码将起作用

      List<int> idList = new List<int> { 1, 5, 6};
      ....
      string someId = "CustomId";
      q = q.Where("@0.Contains(" + someId + ")", idList);
      

      【讨论】:

      • 将这些预定义类型放在哪里。您是要覆盖 DynamicLinq.cs 类还是以某种方式扩展这些类型?
      • @Boris 我的意思是在DynamicLinq.cs编辑这个变量
      • 我已经通过 nuget 安装了 Linq.Dynamic,它是一个库,所以我无法编辑代码。我知道我可以从 github 下载它的源代码然后更改它,但我不想这样做。
      • @Boris 在某些情况下可能有用:-)
      • 我同意。 +1 实用性。
      【解决方案4】:

      @Felipe Oriani 在他 90% 的回答中使用了 string.Contains 方法和 yourId 单个值,但被问到:

      q = q.Where(a =&gt; idList.Contains(a.MyId));

      这是a的成员(属性)访问权限。

      所以这是最终测试的扩展方法:

      /// <summary>
      /// Creates lambda expression predicate: (TEntity entity) => collection.Contains(entity.property)
      /// </summary>
      public static Expression<Func<TEntity, bool>> ContainsExpression<TEntity, TProperty, TCollection>(
          this TCollection collection, 
          Expression<Func<TEntity, TProperty>> property
      )
          where TCollection : ICollection<TProperty>
      {
          // contains method
          MethodInfo containsMethod = typeof(TCollection).GetMethod(nameof(collection.Contains), new[] { typeof(TProperty) });
      
          // your value
          ConstantExpression collectionInstanceExpression = Expression.Constant(collection);
      
          // call the contains from a property and apply the value
          var containsValueExpression = Expression.Call(collectionInstanceExpression, containsMethod, property.Body);
      
          // create your final lambda Expression
          Expression<Func<TEntity, bool>> result = Expression.Lambda<Func<TEntity, bool>>(containsValueExpression, property.Parameters[0]);
      
          return result;
      }
      

      例子:

      List<int> idList = new List<int> { 1, 5, 6 };
      
      Expression<Func<MyEntity,int>> idExpression = entity => entity.Id;
      var contains = idList.ContainsExpression(idExpression)
      
      IQueryable<MyEntity> q = DbContext.Set<MyEntity>().Where(contains);
      

      【讨论】:

      • 我更喜欢这个解决方案。但是当我使用 string[] 而不是 List 时会出现问题,因为它找不到 Contains 方法。我通过更改为 var containsMethod = typeof(ICollection&lt;TProperty&gt;).GetMethod(nameof(ICollection&lt;TProperty&gt;.Contains), new[] { typeof(TProperty) }); 来解决此问题
      【解决方案5】:

      另一种给这只猫剥皮的方法是将包含转换为 OR。

      someArray.Constains(someField) 等价于:

      someField == someArray[0] 或 someField == someArray[1] 等等。

      这并不理想,但如果数组很小,它可以工作。

      【讨论】:

        猜你喜欢
        • 2019-06-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多