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 :)
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".
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);
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.
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
.ExceptBy(newForm.FormItems,it=>it.ItemId);
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
Related
I am using below code to calculate score, on list of question answer by user, but getting error sequence contains no matching element.
Now here I am trying to select first selected option of question and doing sum of score of that option. But when there is no option selected it says no matching element found hence I added DefaultIfEmpty but it still throws same error.
Now If I use FirstOrDefault, I am getting null values, then I have to add null check in Sum, which doesn't look appropriate. Should I provide my default in FirstOrDefault is that solution?
int Score = Questions.Select(x => x.Options.First(o => o.IsSelected))
.DefaultIfEmpty()
.Sum(s => s == null ? 0 : s.Score);
So what is best way to write this linq query.
As your code is written, you're calling DefaultIfEmpty on the set of options for all questions, thus it will have no effect as long as the set of questions is not empty. And given that a question with no selected options exists, as you have pointed out, the inner call to First will throw.
If you want to use DefaultIfEmpty, you could do it on the options collection as follows:
int Score = Questions.Select(x => x.Options
.Where(o => o.IsSelected)
.Select(o => o.Score)
.DefaultIfEmpty()
.First()
)
.Sum();
Here, since you project to an integer collection before calling DefaultIfEmpty, it will yield a singleton collection with 0 in it in the empty case, as 0 is the default value for int. However, FirstOrDefault will achieve the same thing as the calls to DefaultIfEmpty().First() in this case.
I've got a List with 1,000,000 complex objects. I need to create another list with a subset of those objects, keeping the original list unchanged. At this point in the code, I know for certain that bigList is not null, and that it has at least 1 item.
My original code:
var smallList = bigList.Where(csvrec => csvrec.PreApprovalAmount <= 0 || csvrec.PreApprovalAmount > csvrec.ReplacementAmount).ToList();
My team lead said that there were several problems with my code. He said that .Where could result in null, and that calling .ToList() would cause a null exception. So, in order to avoid that, he said I needed to change my code to:
var smallList = new List<CSVLines>();
if(bigList.Any(csvrec => csvrec.PreApprovalAmount <= 0 || csvrec.PreApprovalAmount > csvrec.ReplacementAmount))
{
smallList = bigList.Where(csvrec => csvrec.PreApprovalAmount <= 0 || csvrec.PreApprovalAmount > csvrec.ReplacementAmount).ToList();
}
I don't think that .Where can ever result in a null exception.
I don't think that smallList will not ever be null. It could be a
list with 0 elements, but not null.
Doing .Any with a predicate means it has to generate the list, then
determine if it has at least 1 element, and then my code will have
to generate the same list again to assign it to smallList.
Am I correct? Are the proposed changes from my team lead basically doubling the amount of work to create this list with no real benefit?
Does .Any with a predicate have to generate the resulting list before
it looks for .Any
No, Enumerable.Any does not need to do that. MSDN:
The enumeration of source is stopped as soon as the result can be
determined.
The method takes a sequence and a predicate, then enumerates the sequence until the predicate matches once and returns true. If no item matches false is returned. So if the first item already matches the result does not need to be enumerated. In Source-code:
foreach (TSource element in source) {
if (predicate(element)) return true;
}
return false;
Are the proposed changes from my team lead basically doubling the
amount of work to create this list with no real benefit?
Yes, first checking if any item matches and then using Where to filter is unnecessary overhead in this case. It is not doubling the overhead because Any stops at the first matching but it is overhead (it's doubled if there is no matching item, because the sequence has to be enumerated twice).
.Where could result in null, and that calling .ToList() would cause
a null exception.
No, that's impossible. Enumerable.Where never returns null, it's a filter on the input sequence and if no item matches the predicate Enumerable.Empty<T> is returned.
Maybe he was confused because a query gets executed at the ToList, so if there was a NullReferenceException somewhere in the query, then you see this exception at the ToList(or any other method that executes it). Look at following query that throws an exception:
var query = "foo".Select(c => { throw new NullReferenceException(); return 1; });
List<int> list = query.ToList(); // exception here not in first line
He said that .Where could result in null, and that calling .ToList() would cause a null exception.
The only reason a Where could cause a null reference exception is if its target is null or its predicate runs into a null reference exception. If nothing matches, .Where returns an empty IEnumerable<T>, not a null.
Adding a call to Any is completely superfluous. Although it does not recreate the entire list, it still uses some CPU cycles in evaluating the predicate before returning true or false. It is still O(n), though, so when no match is found, the condition is evaluated for the entire sequence.
Adding a call to Any also decreases readability of your code, which is arguably worse than wasting CPU cycles.
.Where() should only cause an exception if bigList was null to begin with.
However, doing .Any() does not have to generate the list. It will go through a collection and stop as soon as first item that matches the predicate is found.
The one change you probably should make is to remove the ToList() call. Try to stick with IEnumerable as much as possible and avoid creating the List objects, especially when working with larger sets. This is to improve memory use and avoid causing OutOfMemoryExceptions rather than NullReferenceExceptions.
I have a an
IEnumerable<KeyValuePair<string,string>>
within this IEnumerable I have a Key called "JobDetails" In order to ensure this key is there I am using the following statement;
var avalue = from val in tokensReturned where val.Key == "JobDetails" select val.Key;
which I would expect to pass this Assert as the Key is there
Assert.AreEqual("JobDetails", avalue);
The message I am getting is Expected: 'J' But was "JobDetails"
Is also states that the values differ at index 0
if I add .First() to avalue in the assert then the test passes, is this the correct thing to do? I am a bit of a LINQ newbie and don't want to add .First because it works, I want to know what the right thing to do is.
Thanks in advance
Yes, you have to add a .First()... The select will return another IEnumerable<> with as many elements as there are elements in tokensReturned with Key == "JobDetails". An IEnumerable<T> with 0 or 1 elements is still an IEnumerable<T>. It doesn't implicitly/automatically "decade" to its "generic" T type (string in your case). By using .First() you extract the first element of the IEnumerable<T>, that is a T object. If there are no elements, you'll get an exception.
If you are sure there should be a single element, I often suggest using .Single(), that will check that there are exactly 1 element (this is an additional check, useful when you work with DB, where what you think should return 1 element doesn't always return 1 element, because what you think is contained in the DB isn't exactly what is contained in the DB)
Since you're already filtering out only those items that match, you only need to check how many you have:
Assert.AreEqual(1, avalue.Count());
If you try to extract the first item with First() or Single(), you run the risk of your test throwing a non-assert exception because you don't have a first (or single) item.
Im new to Linq.
I like this simple lambda expression :
crmContext.CallForTags.FirstOrDefault(x => x.CallForText.Contains(callAgainForText)).RowID;
which returns me what i want in a single line.
however there is similar linq expression:
crmContext.CallForTags.Where(x => x.CallForText.Contains(callAgainForText)).Select(x => x.RowID);
i expect this would do the same and return me RowID
but it actually give me error saying :
cannot implicitly convert IQueryable to int
i tried searching on net. but couldnt find similar post??
Can any one help me understand , how does it differ?
Also is there any performance overhead using between two?
and which one is more better approach with regards to performance?
Select returns an IEnumerable<T>. If there is only one element, it will return an IEnumerable<T> with one element.
http://msdn.microsoft.com/en-us/library/system.linq.enumerable.select.aspx
FirstOrDefault returns a single instance of T.
http://msdn.microsoft.com/en-us/library/system.linq.enumerable.firstordefault.aspx
With your first query, you are saying "give me the RowID of the first row where CallForText contains the value of callAgainForText (or the default for the type if none exists)."
With your second query, you are effectively saying "give me the RowIDs for every row where CallForText contains the value of callAgainForText." This will always give you back an IEnumerable, even if it only contains a single item.
You are then trying to assign the result of this query - which returns multiple items - to a single instance of T, which of course, doesn't work.
This portion will give you a complete row
//some IQueryable = crmContext.CallForTags.Where(x => x.CallForText.Contains(callAgainForText));
now as you are using select to have a value of single column then try taking it in a int variable like above ait wont work as it return an IEnumerable
Int32 test = crmContext.CallForTags.Where(x => x.CallForText.Contains(callAgainForText)).RowID;
When you use the where then column name it gives a single value, or FirstorDefault() for complete row.
FirstOrDefault returns the first element that matches the condition, or null.
when you use select is returns an IEnumerable of the elements you've specified in your lambda expression, SingleOrDefault or FirstorDefault will return the actual type not an enumerable of it.
In the first expression, you ask to be returned only one result if there are any and accessing its RowIDproperty (possible NullReferenceException there), which I assume is of int type.
In the second expression you are asking to filter a list based on your condition and then asking to retrieve RowID properties of each item in the result. I assume you want to assign it to a variable of type int which requires an implicit conversion from IQueryable to int, therefore an error.
Assuming that RowID is an int, then:
crmContext.CallForTags.FirstOrDefault(x => x.CallForText.Contains(callAgainForText)).RowID
will return an int, namely RowID. However, FirstOrDefault() returns null for a reference type if the sequence is empty, which means that it will throw a NullReferenceException when you try to dereference .RowID in that case.
crmContext.CallForTags.Where(x => x.CallForText.Contains(callAgainForText)).Select(x => x.RowID);
will return a sequence of all the RowID values for all the items that match the predicate x.CallForText.Contains(callAgainForText).
Note that is a sequence, not an int! (By "sequence" I mean IEnumerable<T>.)
FirstOrDefault returns the first element that matches the predicate, or null (If no matches are found). The return type will be an int (if the predicate returns true) since you are selecting the first element only, or null will be returned if the predicate returns false.
rmContext.CallForTags.Where(x => x.CallForText.Contains(callAgainForText)).Select(x => x.RowID);
Selects all items that contain callAgainForText and returns an IEnumerable of integers (Which are the Row ID's) or more specifically IEnumerable<int>
I need to provide a null where clause that has no effect.
Currently I have:
f=>{f!=null;}
However that doesn't really look right. If I were to Select clients, I use
.Select(clients => clients)
With my filter I also get a warning about not all code paths returning a result.
Just return true:
foo.Where(f => true)
Your lambda expression doesn't work for three reasons:
You're trying to use f != null as a statement, which it isn't.
You don't have a return value.
It would reject null values.
The first two can be fixed by removing the braces:
foo.Where(f => f != null)
The last point means it's not really a no-op filter, which is what I guess you meant by "identity filter". Whether it's what you really want or not though, I can't say.