3

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.

Randy Slavey
  • 544
  • 4
  • 19
Nathan Tregillus
  • 6,006
  • 3
  • 52
  • 91

2 Answers2

2

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);
lucky
  • 12,734
  • 4
  • 24
  • 46
  • 1
    thanks, i see this works on my in memory objects, but i am currently trying to use it in conjunction with LinqToSalesforce but it fails to be evaluated. Back to the drawing board. my solution would be so easy if this library implmented a contains method. thanks for your help. – Nathan Tregillus Jan 26 '18 at 18:56
  • See my solution, using contains. – Randy Slavey Jan 26 '18 at 19:10
  • In the last line, when you put predicate inside "Where", may you need compile the built expression: `mySet.Where(predicate.Compile())` – Fer R Mar 25 '20 at 15:06
0

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}");
        }
    }
}
Randy Slavey
  • 544
  • 4
  • 19