代码之家  ›  专栏  ›  技术社区  ›  Tigran

用LINQ筛选集合

  •  9
  • Tigran  · 技术社区  · 15 年前

    假设我们有一组人对象

    class Person 
    {
         public string PersonName {get;set;}
         public string PersonAddress {get;set;}    
    }
    

    在代码定义的集合中

    List<Person> pesonsList = new List<Person>();
    

    我们需要一个过滤器来过滤集合并将结果返回给最终用户。假设我们有一个过滤器类型对象的集合

    class Filter 
    {
        public string FieldName {get;set;}
        public string FilterString {get;set;}
    }
    

    在代码的某个地方

    List<Filter> userFilters = new List<Filter>(); 
    

    所以我们需要通过在userfilters集合中定义的过滤器来过滤personslist集合的内容。何处 filter.fieldname==“人员姓名”filter.fieldname==“人员地址” .我怎样才能用一种很酷的方式处理Linq呢?开关/外壳等解决方案,或 我想,可能是从文件名中确定要查找的人的属性的PersonsList上的扩展方法。还有别的吗?有些棘手的事情:) 谢谢您。

    4 回复  |  直到 10 年前
        1
  •  9
  •   Chris Barlow Pete Lomax    13 年前

    可以使用 Expression 班级。

    public static Expression<Func<TInput, bool>> CreateFilterExpression<TInput>(
                                                       IEnumerable<Filter> filters)
    {
        ParameterExpression param = Expression.Parameter(typeof(TInput), "");
        Expression lambdaBody = null;
        if (filters != null)
        {
            foreach (Filter filter in filters)
            {
                Expression compareExpression = Expression.Equal(
                        Expression.Property(param, filter.FieldName),
                        Expression.Constant(filter.FilterString));
                if (lambdaBody == null)
                    lambdaBody = compareExpression;
                else
                    lambdaBody = Expression.Or(lambdaBody, compareExpression);
            }
        }
        if (lambdaBody == null)
            return Expression.Lambda<Func<TInput, bool>>(Expression.Constant(false));
        else
            return Expression.Lambda<Func<TInput, bool>>(lambdaBody, param);
    }
    

    使用此助手方法,可以在任何 IQueryable<T> 类,因此这应该适用于每个Linq后端:

    public static IQueryable<T> Where<T>(this IQueryable<T> source, 
                                              IEnumerable<Filter> filters)
    {
        return Queryable.Where(source, CreateFilterExpression<T>(filters));
    }
    

    …你可以这样称呼它:

    var query = context.Persons.Where(userFilters);
    

    如果你想支持 IEnumerable<T> 同时,您还需要使用这个额外的扩展方法:

    public static IEnumerable<T> Where<T>(this IEnumerable<T> source, 
                                               IEnumerable<Filter> filters)
    {
        return Enumerable.Where(source, CreateFilterExpression<T>(filters).Compile());
    }
    

    注意,这只适用于字符串属性。如果要筛选字段,则需要更改 Expression.Property 进入之内 Expression.Field (或) MakeMemberAccess ,如果需要支持字符串属性以外的其他类型,则必须向 Expression.Constant 部分 CreateFilterExpression 方法。

        2
  •  3
  •   kevingessner    15 年前

    你可以通过反射来做到:

    IQueryable<Person> filteredPersons = personsList.AsQueryable();
    Type personType = typeof(Person);
    foreach(Filter filter in userFilters) {
        filteredPersons = filteredPersons.Where(p => (string)personType.InvokeMember(filter.FieldName, BindingFlags.GetProperty, null, p, null) == filter.FilterString);
    }
    

    (未编译,但这应该是正确的路线)

        3
  •  2
  •   Carlos Landeras Yuriy Galanter    10 年前

    你不能就这么做吗

    personList.Where(x => x.PersonName == "YourNameHere").ToList() ?
    
        4
  •  0
  •   Bojan Resnik    15 年前

    我将添加一个方法到 Filter 类以检查是否满足筛选器:

    class Filter 
    {
        public string FieldName {get;set;}
        public string FilterString {get;set;}
    
        public bool IsSatisfied(object o)
        { return o.GetType().GetProperty(FieldName).GetValue(o, null) as string == FilterString;
    }
    

    然后您可以这样使用它:

    var filtered_list = personsList.Where(p => userFilters.Any(f => f.IsSatisfied(p)));