可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have a list that i would like to filter based on a List of KeyValuePairs. All the keys in the KeyValuePair exist in the object.
So let's say i have a list of objects from this class:
public class filters
{
public string Name { get; set; }
public string Age { get; set; }
public string Country { get; set; }
}
And I have a KeyValuePair with:
Key: "Name", Value: "test"
Key: "Country", Value: "SE"
Is it possible to generate some kind of LINQ predicate from the KeyValuePair that is usable as list.Where(predicate)
, and the predicate would be the same as if I would have written list.Where(c => c.Name == "test" && c.Country == "SE")
?
Or how should I approach this?
回答1:
As a one-liner:
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));
This enables you to have an unlimited number of filters.
For each item
in your list
the All
predicate will check the validity for each filter. item.GetType()
gets the Type
(ie information about your class) for your item
. GetProperty(f.Key)
gets information for the specific property, named by the Key
of the current filter f
. GetValue(item)
gets the value of the property for the current item
. The ?
is a new feature of c# 6, ie it's an inline check for null
, ie if the property is not found, it does not try to execute GetValue
-- which would raise a NullReferenceException
-- but returns null
. You then have to cast the property value to string
and compare it to the Value
of the current filter. You can also use String::Compare (or any other comparison method you prefer).
All
only returns true
if all filters are met and false otherwise. So the result of this query will contain all elements which meet all the filters in your Dictionary
回答2:
Something like this? Getting the Propertyname through reflection and do an equality check.
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:
I might have mis understood you here but does this do what you want:
// 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"];
... or you can wrap the predicate in an extension method ...
public IEnumerable<filters> ByNameAndCountry(this IEnumerable<filters> collection, string name, string country)
{
return collection.Where(i => i.Name == name && i => i.Country == country);
}
... having done that you can filter the original list like this ...
var result = filterCollection.ByNameAndCountry("test", "ES");
回答4:
This should do the trick:
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;
}
Result:
回答5:
You want a Predicate<filters>
generated from KeyValuePair, which is IEnumerable<KeyValuePair<string, string>>
. So it's
Func<IEnumerable<KeyValuePair<string, string>>, Predicate<filters>>
Use reflection to enumerate All
properties listed in KeyValuePair.Key and check if each property value match KeyValuePair.Value. Full code is like
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:
You could use an approach like this:
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;
}
}
You could also extend the ToPredicate
method to combine the KeyValuePairs
with something else than and
.