19

Is there any disadvantage in concatenating multiple Where in LINQ instead of using a single Where with multiple conditions?

I'm asking because using multiple Where can help to reduce complexity and improve maintainability of my code considerably.

Consider following code, chargeList is a List<Charge> which is the source of a BindingSource:

IEnumerable<Charge> matchingCharges = chargeList;
if(!string.IsNullOrWhiteSpace(channelAbbr))
    matchingCharges = matchingCharges
        .Where(c => c.ChannelAbbreviation == channelAbbr);
if(deliveryNoteDate.HasValue)
    matchingCharges = matchingCharges
        .Where(c => c.ArrivalAt == deliveryNoteDate.Value);
if(chargeID.HasValue)
    matchingCharges = matchingCharges
        .Where(c => c.ChargeID == chargeID.Value);

This concise code will handle all combinations of filter, none,one,two,all.

Otherwise i'd have to use if-else and multiple conditions in a single Where.

This is the best that comes to my mind:

// important to keep code readable:
bool filterChannel = !string.IsNullOrWhiteSpace(channelAbbr);
bool filterDate = deliveryNoteDate.HasValue;
bool filterID = chargeID.HasValue;

if(!filterChannel && !filterDate && !filterID)
{
    // take all 
    matchingCharges = chargeList;
}
else
{
    matchingCharges = chargeList
        .Where(c => 
            filterChannel ? c.ChannelAbbreviation == channelAbbr : true
            && filterDate ? c.ArrivalAt == deliveryNoteDate.Value : true
            && filterID   ? c.ChargeID ==  chargeID.Value : true);
}

So what are the differences between both, are they negligible? Does the LINQ provider matter?

Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939

2 Answers2

16

Semantically, there is no difference in the case of Where (contrast OrderBy, which needs more care). At the implementation level, it is simply multiple predicates with simple expression trees instead of a single predicate with a complex expression tree; but most engines will cope fine with either.

For what you are doing, multiple Where is ideal.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • *instead of a single predicate with a complex expression tree* Note that if you use LINQ to Objects, the predicates of multiple `Where` statements will be combined into a single predicate, and only one `foreach` loop will get invoked, regardless the number of `Where` clauses that are chained together. – sloth May 15 '14 at 13:44
  • 2
    @sloth well, that's a *bit* dependent on what else is going on; but yes, in *many* (by no means all) cases the `Iterator.Where(...)` method will be used to combine them, reducing the loops. – Marc Gravell May 15 '14 at 13:50
  • @sloth can you provide a source for the combining of predicates part? – sgarg Nov 07 '17 at 16:31
  • 1
    @sgarg Sure, take a look [here](https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,165). Calling `Where` on a `WhereEnumerableIterator` (e.g. by combining `Where` calls) will result in a single `Iterator` being used and all predicates simply "combined" with `&&` – sloth Nov 08 '17 at 08:41
12

I was wondering the same thing. That's why I tried this in my own application.

I have a list with a lot of entries and this is what I tried:

//TEST 1
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
var hoursLinq = _hourDataSource.Hours
            .Where(hour => hour.Id == profile.Id)
            .Where(hour => hour.DayName.Equals("Maandag"))
            .Where(hour => hour.Day == 1)
            .Select(hour => hour);
stopWatch.Stop();
// Get the elapsed time as a TimeSpan value.
TimeSpan ts1 = stopWatch.Elapsed;

//TEST 2
stopWatch = new Stopwatch();
stopWatch.Start();
var hoursLinq2 = _hourDataSource.Hours
            .Where(hour => hour.Id == profile.Id)
            .Select(hour => hour);

if (hoursLinq2.Count() != 0)
{
    var hoursLinq3 = _hourDataSource.Hours
            .Where(hour => hour.DayName.Equals("Maandag"))
            .Select(hour => hour);

    if (hoursLinq3.Count() != 0)
    {
        var hoursLinq4 = _hourDataSource.Hours
            .Where(hour => hour.Day == 1)
            .Select(hour => hour);
    }
}

stopWatch.Stop();
// Get the elapsed time as a TimeSpan value.
TimeSpan ts2 = stopWatch.Elapsed;

//TEST 3
stopWatch = new Stopwatch();
stopWatch.Start();
var hoursLinq5 = _hourDataSource.Hours
            .Where(hour => hour.Id == profile.Id &&
                            hour.DayName.Equals("Maandag") &&
                            hour.Day == 1)
            .Select(hour => hour);

stopWatch.Stop();
// Get the elapsed time as a TimeSpan value.
TimeSpan ts3 = stopWatch.Elapsed;

Each timespan (ts1, ts2, ts3) had such a small difference in elapsed time that I'm pretty sure you can ignore it.

I guess it's personal preference, I like the multiple where's because of it's readability

DerpyNerd
  • 4,743
  • 7
  • 41
  • 92