By dataCruncher02

2018-04-16 12:13:11 8 Comments

I was wondering if any one can help explain this expression with two lists,

originalForm.FormItems.Where(x =>
     newForm.FormItems.All(y => y.ItemId != x.ItemId));

I think it is supposed to return items that are in originalForm but not in newForm, however, when I try and understand it myself I think it's saying the opposite. If someone could break it down for me, it'd be a big help.

Thanks in advance :)


@Panagiotis Kanavos 2018-04-16 12:39:13

The set operation you are looking for is subtraction, which is implemented by LINQ's Except method. You want the form items in Form 1 minus the items in form 2 based on their IDs.

In SQL you'd use the NOT IN (...) clause to achieve the same

When you don't have an explicit subtraction operator, you cam implement subtraction by including ALL items that don't match a mismatch condition (like your code), or excluding ANY that match an equality condition.

The most common version of Enumerable.Except compares objects using their equality operators. You can use this to find the unique ItemIDs and then retrieve the form items.

Another overload allows you to specify a comparer. Unfortunately there's no built-in way to specify a comparison predicate, so you have to create a custom comparer class that inherits from eg EqualityComparer and compares only the ItemId properties.

A better option is to use the ExceptBy that allows you to specify the key used for comparison, eg :

var uniqueItems=originalForm.FormItems

You can add MoreLINQ as a binary NuGet package or you can add the code to your project by adding the MoreLinq.Source.MoreEnumerable.ExceptBy source package

@Patrick Hofman 2018-04-16 12:15:31

An equivalent of your code that is a little more clear is this code using Except:

var l = originalForm.Select(x => x.ItemId).Except(newForm.FormItems.Select(y => y.ItemId));

This will only get you the item IDs. You can look the actual items back later on if you need them:

originalForm.Where(x => l.Contains(x.ItemId));

Your code "where not any" is "where none", which is the same as "except".

@Zohar Peled 2018-04-16 12:19:53

I think it should be originalForm.Where(x => !newForm.Select(x => x.ItemId).Contains(x.ItemId));, since the OP is looking for items in the originalForm that's not in the newForm. Anyway, Contains is much more sensible then All or my alternative with Any. +1.

@Patrick Hofman 2018-04-16 12:20:36

That is the purpose of Except, right? (A-B) @ZoharPeled

@Patrick Hofman 2018-04-16 12:20:59

@ZoharPeled I saw your answer too, not sure why it was downvoted as it seems a decent alternative.

@Zohar Peled 2018-04-16 12:21:16

Yes, but Except would only return the ItemIds...

@Zohar Peled 2018-04-16 12:23:51

The funny thing is that is was downvoted literally the second I posted it. I didn't even get the chance of loading your answer when mine already got the -1. I was actually going to edit it to include the contains option as well (didn't think Except is going to do much good since the condition is on a single property), but then I saw your answer so I figured what the hell, I'll just delete mine.

@Patrick Hofman 2018-04-16 12:25:01

You might want to consider undeleting.

@Mardoxx 2018-04-16 12:26:28

This is not an answer to what was asked. This just proves an alternate solution to the problem which raised OP's question!

@Patrick Hofman 2018-04-16 12:26:50

Why not? @Mardoxx

@Panagiotis Kanavos 2018-04-16 12:28:26

@Mardoxx on the contrary, this does answer the question by providing clearer equivalent code. The OP asked for the difference between sets using the wrong wording. The difference between sets is implemented by Except in LINQ and SQL. ALL and ANY are ways to implement set difference without using the set difference operation. Databases like SQL Server recognize this and often simplify such code to the same execution plan

@Mardoxx 2018-04-16 12:37:20

@PatrickHofman because it doesn't teach anything other than "to get the difference of the sets use this method", it doesn't explain why what OP wrote works when they think it shouldnt. Hopefully it gives the intuition to arrive at a better understanding though!!

@Zohar Peled 2018-04-16 12:37:34

Well, I took your advice (and Mardoxx's as well)

@Patrick Hofman 2018-04-16 12:38:49

@Mardoxx Updated.

@Zohar Peled 2018-04-16 12:16:09

You are correct, that is what it does, and it is a strange implementation.
I would use Any instead of All:

originalForm.FormItems.Where(x =>
 !newForm.FormItems.Any(y => y.ItemId == x.ItemId));

I think it's much more readable.

The All method will return true if all of the elements in the IEnumerable returns true for the given predicate.

Taken from the source code of Enumerable.cs, here is how it's implemented.

    public static bool All<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
        if (source == null) throw Error.ArgumentNull("source");
        if (predicate == null) throw Error.ArgumentNull("predicate");
        foreach (TSource element in source) {
            if (!predicate(element)) return false;
        return true;

So basically, it will return true only if all the elements of the newForm.FormItems have an ItemId property that is different then the current element from originalForm.FormItems being tested currently in the Where.

The Any method is the exact opposite - it will return true if at least one of the elements in the IEnumerable returns true.

There is another option which is even simpler - and that's to use Contains:

originalForm.FormItems.Where(x => !newForm.FormItems.Contains(y => y.ItemId == x.ItemId);

@Franck 2018-04-16 12:30:06

You can use a negation of the Any expression while checking for equality. Like that it might not always check all the items of the second collection.

originalForm.FormItems.Where(x =>
 !newForm.FormItems.Any(y => y.ItemId == x.ItemId));

Any will return as soon as it find 1 item that match the expression. So this create 2 scenarios.

Scenario 1 : If the original id exist in the second list the Any will find the first occurrence and return true, Then it get negated to false and so it doesn't pick the item in the return results.

Scenario 2 : If the original id does not exist in the second list the Any will test all the items of the second list and wont find any so it will return false, Then it get negated to true and so the item is in the return results.

Related Questions

Sponsored Content

7 Answered Questions

[SOLVED] Group by in LINQ

  • 2011-09-06 19:44:20
  • test123
  • 860473 View
  • 758 Score
  • 7 Answer
  • Tags:   c# linq group-by

18 Answered Questions

[SOLVED] Dynamic LINQ OrderBy on IEnumerable<T>

35 Answered Questions

[SOLVED] What is the Java equivalent for LINQ?

  • 2009-08-01 18:53:26
  • Ahmed
  • 219155 View
  • 728 Score
  • 35 Answer
  • Tags:   java linq

21 Answered Questions

[SOLVED] LINQ query on a DataTable

6 Answered Questions

[SOLVED] Multiple "order by" in LINQ

  • 2008-11-18 13:34:11
  • Sasha
  • 484050 View
  • 1340 Score
  • 6 Answer
  • Tags:   linq sql-order-by

25 Answered Questions

[SOLVED] Why not inherit from List&lt;T&gt;?

11 Answered Questions

[SOLVED] LINQ Aggregate algorithm explained

  • 2011-08-18 09:51:21
  • Alexander Beletsky
  • 181843 View
  • 547 Score
  • 11 Answer
  • Tags:   c# .net linq

7 Answered Questions

[SOLVED] Concat all strings inside a List<string> using LINQ

  • 2009-02-18 00:56:57
  • Jobi Joy
  • 302524 View
  • 421 Score
  • 7 Answer
  • Tags:   c# linq .net-3.5

14 Answered Questions

[SOLVED] When to use .First and when to use .FirstOrDefault with LINQ?

  • 2009-06-21 19:15:29
  • Metro Smurf
  • 401755 View
  • 639 Score
  • 14 Answer
  • Tags:   c# .net linq

5 Answered Questions

[SOLVED] Curious null-coalescing operator custom implicit conversion behaviour

Sponsored Content