I have a collection where I want to programatically add OR condtions to a linq query. I know how to add AND condtions like the following:
var mySet = SomeFactory.GetData();
foreach(var nameFilter in nameFilters)
{
mySet = mySet.Where(item => item.Name == nameFilter);
}
foreach(var ageFilter in ageFilters)
{
mySet = mySet.Where(item => item.Age == ageFilter)
}
however, if I wanted these to be OR conditions rather than be 'AND' together, how would I do that? I.E. I can do this if I know I will always have both and never an array of different values:
mySet.Where(item => item.Name == nameFilter[0] || item.Name == nameFilter[1] ... || item.Age == ageFilter[0] || item.Age == ageFilter[1] || ...);
TL;DR: I want to be able to chain an unknown number of boolean checks into a single expression evaluated with OR statements. For example, if I have a cross reference of People named Mary or Jim who are either 32 or 51.
PredicateBuilder would help you to apply where clauses in flexibility. You can find extension method here.
var filterOfNames = new List<string> {"Sample", "Sample2"};
var filterOfAges = new List<int> { 21, 33, 45 };
var mySet = SomeFactory.GetData();
var predicate = PredicateBuilder.True<TypeOfmySet>();
foreach (var filterOfName in filterOfNames)
{
//If it is the first predicate, you should apply "And"
if (predicate.Body.NodeType == ExpressionType.Constant)
{
predicate = predicate.And(x => x.Name == filterOfName);
continue;
}
predicate = predicate.Or(x => x.Name == filterOfName);
}
foreach (var filterOfAge in filterOfAges)
{
//If it is the first predicate, you should apply "And"
if (predicate.Body.NodeType == ExpressionType.Constant)
{
predicate = predicate.And(x => x.Age == filterOfAge);
continue;
}
predicate = predicate.Or(x => x.Age == filterOfAge);
}
//I don't know the myset has which type in IQueryable or already retrieved in memory collection. If it is IQueryable, don't compile the predicate otherwise compile it.
//var compiledPredicate = predicate.Compile();
mySet = mySet.Where(predicate);
There is no additive "or" in LINQ. Combining "Where" expressions is always evaluated as "and".
However, you can build predicates, add them to a list, then test them. I think this code does what you want:
using System;
using System.Collections.Generic;
class Item
{
internal string Name { get; set; }
internal short Age { get; set; }
internal string City { get; set; }
}
public static class ExtensionMethods
{
public static List<T> FindAll<T>(this List<T> list, List<Predicate<T>> predicates)
{
List<T> L = new List<T>();
foreach (T item in list)
{
foreach (Predicate<T> p in predicates)
{
if (p(item)) L.Add(item);
}
}
return L;
}
}
class Program
{
static void Main(string[] args)
{
List<Item> items = new List<Item>();
items.Add(new Item { Name = "Bob", Age = 31, City = "Denver" });
items.Add(new Item { Name = "Mary", Age = 44, City = "LA" });
items.Add(new Item { Name = "Sue", Age = 21, City = "Austin" });
items.Add(new Item { Name = "Joe", Age = 55, City = "Redmond" });
items.Add(new Item { Name = "Tom", Age = 81, City = "Portland" });
string nameFilter = "Bob,Mary";
//string ageFilter = "55,21";
string ageFilter = null;
string cityFilter = "Portland";
List<Predicate<Item>> p = new List<Predicate<Item>>();
if (nameFilter != null)
{
p.Add(i => nameFilter.Contains(i.Name));
}
if (cityFilter != null)
{
p.Add(i => cityFilter.Contains(i.City));
}
if (ageFilter != null)
{
p.Add(i => ageFilter.Contains(i.Age.ToString()));
}
var results = items.FindAll(p);
foreach (var result in results)
{
Console.WriteLine($"{result.Name} {result.Age} {result.City}");
}
}
}
Related
Condition 1: In below code sometime list2 is null, hence I make a check like,
list2 == null ? 0 : list2.Wher.....
Condition 1: Some of list1 name also not in list2, for that I want to set Age = 0
how to satisfy both condition?
class Program
{
static void Main(string[] args)
{
var list1 = new List<List1> { new List1 { Name = "A1" }, new List1 { Name = "A2" } };
//sometime my "list2" is null as well
//var list2 = null;
var list2 = new List<List2> { new List2 { Name = "A1", Age = 10 } };
foreach (var a in list1)
{
var X = list2 == null ? 0 : list2.Where(x => x.Name == a.Name).FirstOrDefault().Age;
}
}
}
public class List1
{
public string Name { get; set; }
}
public class List2
{
public string Name { get; set; }
public int Age { get; set; }
}
You don't need the foreach loop at all.
you can solve like this:
var x = list2 == null ? 0 :
list2.Where(x => list1.Any(e => x.Name == e.Name)).FirstOrDefault()?.Age ?? 0;
or another variant would be:
var x = list2 == null ? 0 :
list2.FirstOrDefault(x => list1.Any(e => x.Name == e.Name))?.Age ?? 0;
Try this:
foreach (var a in list1)
{
var X = list2 == null ? 0 : (list2.Where(x => x.Name == a.Name).FirstOrDefault()?.Age ?? 0);
}
First, take a look at This Documentation. It explains the .DefaultIfEmpty that I will use.
DefaultIfEmpty will:
Returns the elements of the specified sequence or the specified value in a singleton collection if the sequence is empty.
What you can do in your case to satisfy both conditions in the next:
var X = list2 == null ? 0 : list2.Where(x => x.Name == a.Name).DefaultIfEmpty(new List2 { Age = 0 }).First().Age;
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?
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
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);
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");
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:
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);
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.
I want to distinct a list of objects just based on some properties. These properties are gotten via reflection and some conditions. I searched a lot but cannot found any code snippets or solutions that are able to do a loop in this lambda expression.
List<PropertyInfo> propList = ...
var distinctList = FullList
.GroupBy(uniqueObj =>
{
//do a loop to iterate all elements in propList
})
.Select(x => x.First());
Ok, took me a while to think this one through.
Basically, you can use the Linq GroupBy operator, but you need to use the overload that accepts a custom IEQualityComparer, because you want to verify equality of the objects based on a subset of all their properties.
The subset of properties is stored in a List<PropertyInfo> that you created somewhere else in your code, or that you receive from a service or whatever.
So, implementing IEqualityComparer, then use it with GroupBy:
//Dummy class representing your data.
//
//Notice that I made the IEqualityComparer as a child class only
//for the sake of demonstration
public class DataObject
{
public string Name { get; set; }
public int Age { get; set; }
public int Grade { get; set; }
public static List<PropertyInfo> GetProps()
{
//Only return a subset of the DataObject class properties, simulating your List<PropertyInfo>
return typeof(DataObject).GetProperties().Where(p => p.Name == "Name" || p.Name == "Grade").ToList();
}
public class DataObjectComparer : IEqualityComparer<DataObject>
{
public bool Equals(DataObject x, DataObject y)
{
if (x == null || y == null)
return false;
foreach (PropertyInfo pi in DataObject.GetProps())
{
if (!pi.GetValue(x).Equals(pi.GetValue(y)))
return false;
}
return true;
}
public int GetHashCode(DataObject obj)
{
int hash = 17;
foreach (PropertyInfo pi in DataObject.GetProps())
{
hash = hash * 31 + pi.GetValue(obj).GetHashCode();
}
return hash;
}
}
}
//Then use that in your code:
//
List<DataObject> lst = new List<DataObject>();
lst.Add(new DataObject { Name = "Luc", Age = 49, Grade = 100 });
lst.Add(new DataObject { Name = "Luc", Age = 23, Grade = 100 });
lst.Add(new DataObject { Name = "Dan", Age = 49, Grade = 100 });
lst.Add(new DataObject { Name = "Dan", Age = 23, Grade = 100 });
lst.Add(new DataObject { Name = "Luc", Age = 20, Grade = 80 });
List<DataObject> dist = lst.GroupBy(p => p, new DataObject.DataObjectComparer()).Select(g => g.First()).ToList();
//The resulting list now contains distinct objects based on the `Name` and `Grade` properties only.
I hope this helps you get closer to your solution.
Cheers
You can create expression using the property name with this method:
public static Expression<Func<T, object>> GetPropertySelector<T>(string propertyName)
{
var arg = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(arg, propertyName);
//return the property as object
var conv = Expression.Convert(property, typeof(object));
var exp = Expression.Lambda<Func<T, object>>(conv, new ParameterExpression[] { arg });
return exp;
}
And use like this:
var exp = GetPropertySelector<Person>("PropertyName");
Now you can make a distinct easily:
List<Person> distinctPeople = allPeople
.GroupBy(exp.Compile())
.Select(g => g.First())
.ToList();
I have the a few methods that have similar signature and was trying to convert them into one generic one without the use of interfaces.
public List<MultiSelectDropdown> ConvertListOfJobStatusToDropdownListClickable(List<JobStatus> js) {
var list = new List<MultiSelectDropdown>();
if (js != null && js.Count >= 1) {
list = js.Select(item => new MultiSelectDropdown { Name = item.StatusValue, Value = item.id.ToString() }).ToList();
}
return list;
}
public List<MultiSelectDropdown> ConvertListOfCUsersToDropdownListClickable(List<cUser> users) {
var list = new List<MultiSelectDropdown>();
if (users != null && users.Count >= 1) {
list = users.Select(item => new MultiSelectDropdown { Name = item.User_Name, Value = item.Id.ToString() }).ToList();
}
return list;
}
This is what I would like to do; pass in a list with two properties.
List<MultiSelectDropdown> ddlForClientUsers = ConvertToMultiSelectDropdownList(listOfClientsForUser, n => n.Client_Id, v => v.Client);
List<MultiSelectDropdown> ddlForJobStatus = ConvertToMultiSelectDropdownList(listOfJobStatus, n => n.Id, v => v.JobName);
This is the method I have tried but not sure how to get item.propName and item.propValue to work.
I get "Cannot resolve" propName and propValue in the method below
Is this possible?
public List<MultiSelectDropdown> ConvertToMultiSelectDropdownList<T, TPropertyName, TPropertyValue>(List<T> listOfT, Func<T, TPropertyName> propName, Func<T, TPropertyValue> propValue) {
var list = new List<MultiSelectDropdown>();
if (listOfT != null && listOfT.Count >= 1) {
list = listOfT.Select(item => new MultiSelectDropdown { Name = item.propName, Value = item.propValue }).ToList();
}
return list;
}
public class MultiSelectDropdown {
public string Name { get; set; }
public string Value { get; set; }
public bool IsChecked { get; set; }
}
Because the properties of your MultiSelectDropdown are strings, your functions should return those as well. And to invoke the functions, you have to write them like propName(item) instead of item.propName - that is the property syntax, and you indicated you didn't want to use interfaces.
public List<MultiSelectDropdown> ConvertToMultiSelectDropdownList<T>(List<T> listOfT, Func<T, string> propName, Func<T, string> propValue) {
var list = new List<MultiSelectDropdown>();
if (listOfT != null && listOfT.Count >= 1) {
list = listOfT.Select(item => new MultiSelectDropdown { Name = propName(item), Value = propValue(item) }).ToList();
}
return list;
}
You are really close, with just a slight mistake. The line (reformatted to prevent scrolling):
list = listOfT.Select(item => new MultiSelectDropdown
{
Name = item.propName,
Value = item.propValue
}).ToList();
needs to be:
list = listOfT.Select(item => new MultiSelectDropdown
{
Name = propName(item),
Value = propValue(item)
}).ToList();
I am trying to make a treeView that have items in the following order.
+ Continent
+ Country
+ Province
+ Territory
And, I have a generic class to hold the treeView Items data.
public class TreeViewContinents
{
public List<TreeViewContinents> Children { get; set; }
public TreeViewContinents Parent { get; set; }
public string Name { get; set; }
public string Content { get; set; }
}
Data:
private readonly List<Dictionary<string, object>> continentsList = new List<Dictionary<string, object>>();
/*
continentsList[0] - "Continent", "Asia"
"Country", "Afghanistan"
"Province", "Kabul"
"Territory", "Bagrami"
"Language", "aaa"
"Culture", "bbb"
.
.
.
.
continentsList[n] - "Continent", "North America"
"Country", "Canada"
"Province", "Ontario"
"Territory", "Ottawa"
"Language", "aaa"
"Culture", "bbb
*/
String array based on which treeView Items needs to be generated.
var treeViewItemNames = new[] { "Continent", "Country", "Province", "Territory" };
Code:
List<object> Continents = this.continentsList.Where(
p_oDict => p_oDict.ContainsKey(treeViewItemNames[0]))
.Select( p_oDict => p_oDict[treeViewItemNames[0]])
.Distinct()
.ToList();
var topItems = new List<TreeViewContinents>();
foreach (object continent in Continents)
{
var level1Items = new TreeViewContinents { Name = treeViewItemNames[0], Content = continent.ToString() };
List<object> Countries = this.continentsList.Where(
p_oDict => p_oDict.ContainsKey(treeViewItemNames[0])
&& p_oDict.ContainsKey(treeViewItemNames[1])
&& p_oDict[treeViewItemNames[0]].Equals(continent)
.Select( p_oDict => p_oDict[treeViewItemNames[1]])
.Distinct()
.ToList();
Countries.Sort((p_oU1, p_oU2) => string.Compare(p_oU1.ToString(), p_oU2.ToString(), StringComparison.Ordinal));
foreach (object country in Countries)
{
var level2Items = new TreeViewContinents { Name = treeViewItemNames[1], Content = country.ToString(), Parent = level1Items };
level1Items.Children.Add(level2Items);
List<object> Provinces = this.continentsList.Where(
p_oDict => p_oDict.ContainsKey(treeViewItemNames[0])
&& p_oDict.ContainsKey(treeViewItemNames[1])
&& p_oDict.ContainsKey(treeViewItemNames[2])
&& p_oDict[treeViewItemNames[0]].Equals(continent)
&& p_oDict[treeViewItemNames[1]].Equals(country))
.Select( p_oDict => p_oDict[treeViewItemNames[2]]).Distinct()
.ToList();
Provinces.Sort((p_oU1, p_oU2) => string.Compare(p_oU1.ToString(), p_oU2.ToString(), StringComparison.Ordinal));
foreach (object province in Provinces)
{
var level3Items = new TreeViewContinents { Name = treeViewItemNames[2], Content = province.ToString(), Parent = level2Items };
level2Items.Children.Add(level3Items);
List<object> Territories = this.continentsList.Where(
p_oDict => p_oDict.ContainsKey(treeViewItemNames[0])
&& p_oDict.ContainsKey(treeViewItemNames[1])
&& p_oDict.ContainsKey(treeViewItemNames[2])
&& p_oDict.ContainsKey(treeViewItemNames[3])
&& p_oDict[treeViewItemNames[0]].Equals(continent)
&& p_oDict[treeViewItemNames[1]].Equals(country)
&& p_oDict[treeViewItemNames[2]].Equals(province)).Select(
p_oDict => p_oDict[treeViewItemNames[3]])
.Distinct()
.ToList();
Territories.Sort((p_oU1, p_oU2) => string.Compare(p_oU1.ToString(), p_oU2.ToString(), StringComparison.Ordinal));
foreach (object territory in Territories)
{
var level4Items = new TreeViewContinents { Name = treeViewItemNames[3], Content = territory.ToString(), Parent = level3Items };
level3Items.Children.Add(level4Items);
}
}
}
topItems.Add(level1Items);
}
In the above code, I have four foreach loops to build a treeview. Is it possible to write a single loop to generate the treeView based on the the variable treeViewItemNames, so that in future If i change the variable like
var treeViewItemNames = new[] { "Continent", "Country", "Territory" };
it should generate treeView like this
+ Continent
+ Country
+ Territory
Ideas/ Suggestions please.
I suggest to iterage through continentsList elements.
var treeViewItemNames = new[] { "Continent", "Country", "Province", "Territory" };
var topItems = new List<TreeViewContinents>();
foreach (var continent in continentsList)
{
List<TreeViewContinents> currentLevel = topItems;
TreeViewContinents parentItem = null;
foreach (var sectionTitle in treeViewItemNames)
{
String value = Convert.ToString(continent[sectionTitle]);
TreeViewContinents currentItem = currentLevel.FirstOrDefault(tree => tree.Content == value);
if (currentItem == null)
{
currentItem = new TreeViewContinents { Name = sectionTitle, Content = value };
currentItem.Children = new List<TreeViewContinents>();
if (parentItem != null)
{
currentItem.Parent = parentItem;
}
currentLevel.Add(currentItem);
}
parentItem = currentItem;
currentLevel = currentItem.Children;
}
}
The problem with this code is that there is no sorting in items, but we can sort the resulting TreeViewContinents list. Here is recursive method for sorting:
public List<TreeViewContinents> SortTreeView(List<TreeViewContinents> treeViewList)
{
foreach (var item in treeViewList)
{
if (item.Children.Count > 0)
{
item.Children = SortTreeView(item.Children);
}
}
return treeViewList.OrderBy(it => it.Content).ToList();
}
You can use it after topItems list is full:
topItems = SortTreeView(topItems);