【问题标题】:Filter list based on KeyValuePairs基于 KeyValuePairs 的过滤列表
【发布时间】:2016-05-04 22:31:49
【问题描述】:

我有一个列表,我想根据 KeyValuePairs 列表进行过滤。 KeyValuePair 中的所有键都存在于对象中。

假设我有一个此类对象的列表:

public class filters
{
    public string Name { get; set; }
    public string Age { get; set; }
    public string Country { get; set; }
}

我有一个 KeyValuePair:

Key: "Name", Value: "test"
Key: "Country", Value: "SE"

是否可以从 KeyValuePair 生成某种可用作list.Where(predicate) 的 LINQ 谓词,并且该谓词与我写的 list.Where(c => c.Name == "test" && c.Country == "SE") 相同?

或者我应该如何处理这个问题?

【问题讨论】:

  • 你试过反射吗?
  • 不确定如何使用反射
  • var keySet = new Dictionary<string, string> { { "Name", "test" } }; Predicate<filters> condition = (c) => c.Name == keySet["Name"] && c.Country == keySet["Country"]; list.Where(condition);这是你感兴趣的吗?

标签: c# lambda


【解决方案1】:

作为单行:

var filters = new Dictionary<string, string>{{"Name", "test"}, {"Country", "SE"}};
var result = list.Where(item => filters.All(f => (string)(item.GetType().GetProperty(f.Key)?.GetValue(item)) == f.Value));

这使您可以拥有无​​限数量的过滤器。

对于list 中的每个itemAll 谓词将检查每个过滤器的有效性。 item.GetType() 为您的item 获取Type(即有关您的班级的信息)。 GetProperty(f.Key) 获取特定属性的信息,由当前过滤器fKey 命名。 GetValue(item) 获取当前 item 的属性值。 ? 是 c# 6 的一个新特性,即它是对 null 的内联检查,即如果找不到该属性,它不会尝试执行 GetValue -- 这会引发 NullReferenceException --但返回null。然后,您必须将属性值转换为 string 并将其与当前过滤器的 Value 进行比较。您还可以使用 String::Compare (或您喜欢的任何其他比较方法)。

All 仅在满足所有过滤器时返回 true,否则返回 false。因此,此查询的结果将包含满足字典中所有过滤器的所有元素

【讨论】:

  • 请注意,使用这种方法在处理大型列表时效果不佳。出于好奇,我将此解决方案与我建议的解决方案(使用编译表达式)进行了比较,并使用三个过滤器过滤包含一百万个条目的列表,这种方法需要 ~400 ms 而我的方法需要 ~30 毫秒。但是,对于小型列表,此解决方案会更好地工作(因为构建编译的表达式需要一些时间)。
  • @fknx 是的,当然,这种即时方法的扩展性不如预编译谓词。更喜欢哪一个取决于具体的用例。这只是为了表明,用单线解决问题是可能的,
【解决方案2】:

这样的?通过反射获取 Propertyname 并进行相等性检查。

Func<filters, IEnumerable<KeyValuePair<string, string>>, bool> filter = (filters, pairs) =>
{
    foreach (var valuePair in pairs)
    {
        if (filters.GetType().GetProperty(valuePair.Key).GetValue(filters) != valuePair.Value)
        {
            return false;
        }
    }
    return true;
};

List<filters> list = new List<filters>();
list.Add(new filters() { Name = "Name1", Country = "DE"});
list.Add(new filters() { Name = "Name2", Country = "SE"});

var element = list.FirstOrDefault(x => filter(x, new List<KeyValuePair<string, string>>() {
    new KeyValuePair<string, string>("Name", "Name2"),
    new KeyValuePair<string, string>("Country", "SE"),
}));

Console.WriteLine(element.Name);

【讨论】:

    【解决方案3】:

    我可能在这里误解了你的意思,但这是否符合你的要求:

    // say I have a list of ilters like this 
    // assume there are actually some filters in here though
    var filterCollection = new List<filters>() 
    
    // build dictionary of key values
    var keyedSet = filterCollection.ToDictionary(i => i.Name + i.Country, i => i);
    
    // query them using key ...
    var filterItem = keyedSet["testSE"];
    

    ...或者您可以将谓词包装在扩展方法中...

    public IEnumerable<filters> ByNameAndCountry(this IEnumerable<filters> collection, string name, string country)
    {
         return collection.Where(i => i.Name == name && i => i.Country == country);
    }
    

    ...完成后,您可以像这样过滤原始列表...

    var result = filterCollection.ByNameAndCountry("test", "ES");
    

    【讨论】:

    • 我不想写 i => i.Name + i.Country,这应该是我的 KeyValuePair 生成的。但是我知道我不能简单地使用字符串值来获取 lambda 中的字段,例如 c => c.StringValue == StringValue
    • 嗯...所以您想将谓词包装在扩展方法中? ...这有帮助吗?
    【解决方案4】:

    这应该可以解决问题:

    void Main()
    {
        List<Filter> filters = new List<Filter>() {
            new Filter {Name = "Filter1", Age = 1, Country ="De"},
            new Filter {Name = "Filter2", Age = 2, Country ="Fr"},
            new Filter {Name = "Filter3", Age = 3, Country ="It"},
            new Filter {Name = "Filter4", Age = 4, Country ="Es"},
        };
    
        KeyValuePair<string, string> kvp = new KeyValuePair<string, string>("Filter1", "De");
    
        var result = filters.AsQueryable().Where (GetPredicate(kvp));
        result.Dump();
    }
    //Create the predicate as an expression, which takes a Filter as input and a kvp as a parameter
    private static Expression<Func<Filter, bool>> GetPredicate(KeyValuePair<string,string> kvp)
    {
            return (f) => f.Name == kvp.Key && f.Country == kvp.Value;
    }
    

    结果:

    【讨论】:

      【解决方案5】:

      您想要从 KeyValuePair 生成的 Predicate&lt;filters&gt;,即 IEnumerable&lt;KeyValuePair&lt;string, string&gt;&gt;。原来如此

      Func<IEnumerable<KeyValuePair<string, string>>, Predicate<filters>>
      

      使用反射枚举 KeyValuePair.Key 中列出的All 属性,并检查每个属性值是否与 KeyValuePair.Value 匹配。完整的代码就像

      var lists = new List<filters> { new filters { Name = "abc", Country = "def" } };
      
      Func<IEnumerable<KeyValuePair<string, string>>, Predicate<filters>> predicateBuilder =
      ( keyValueParis ) => filter => ( from kp in keyValueParis
                                       let pi = typeof( filters ).GetProperty( kp.Key )
                                       select pi.GetValue( filter ) == kp.Value )
                                    .All( r => r );
      
      var predicates = new List<KeyValuePair<string, string>>
      {
          new KeyValuePair<string, string>("Name", "abc" ),
          new KeyValuePair<string, string>("Country", "def")
      };
      Predicate<filters> predicate = predicateBuilder( predicates );
      
      Console.WriteLine( lists.FindAll(predicate).Count);
      

      【讨论】:

        【解决方案6】:

        你可以使用这样的方法:

        void Main()
        {
            var keyValuePairs = new List<KeyValuePair>
            {
                new KeyValuePair {Key = "Name", Value = "Test"},
                new KeyValuePair {Key = "Age", Value = "42"},
                new KeyValuePair {Key = "Country", Value = "USA"}
            };
        
            var list = new List<Filter>();
            list.Add(new Filter { Name = "Test", Age = "42", Country = "USA" });
        
            list.Where(keyValuePairs.ToPredicate<Filter>()).Dump();
        }
        
        public class Filter
        {
            public string Name { get; set; }
            public string Age { get; set; }
            public string Country { get; set; }
        }
        
        public class KeyValuePair
        {
            public string Key { get; set; }
        
            public string Value { get; set; }
        }
        
        public static class KeyValuePairExtensions
        {
            public static Func<T, bool> ToPredicate<T>(this IEnumerable<KeyValuePair> keyValuePairs)
            {
                if (keyValuePairs == null || !keyValuePairs.Any())
                    return t => false; // default value in case the enumerable is empty
        
                var parameter = Expression.Parameter(typeof(T));
        
                var equalExpressions = new List<BinaryExpression>();
        
                foreach (var keyValuePair in keyValuePairs)
                {
                    var propertyInfo = typeof(T).GetProperty(keyValuePair.Key);
        
                    var property = Expression.Property(parameter, propertyInfo);
        
                    var value = Expression.Constant(keyValuePair.Value, propertyInfo.PropertyType);
        
                    var equalExpression = Expression.Equal(property, value);
                    equalExpressions.Add(equalExpression);
                }
        
                var expression = equalExpressions.First();
        
                if (equalExpressions.Count > 1)
                {
                    // combine expression with and
                    expression = Expression.AndAlso(equalExpressions[0], equalExpressions[1]);
        
                    for (var i = 2; i < equalExpressions.Count; i++)
                    {
                        expression = Expression.AndAlso(expression, equalExpressions[i]);
                    }
                }
        
                var lambda = (Func<T, bool>)Expression.Lambda(expression, parameter).Compile();
                return lambda;
            }
        }
        

        您还可以扩展ToPredicate 方法以将KeyValuePairsand 以外的其他内容结合起来。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-09-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-04-23
          • 2021-10-16
          • 1970-01-01
          相关资源
          最近更新 更多