What is the difference between firstOrDefault() and select () when selecting single row - c#

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>

Related

Using lambda to select top 1 row returned from DataTable

I have a DataTable called currencyAmounts and using lambda, my expression was working fine until the return had more than one possibility. I am searching the DataTable for a match to my "value" variable. How can I change the following expression to just select the first row returned:
DataRow resultRow = currencyAmounts.AsEnumerable().Single(r => ((decimal)r["OriginalAmount"]) == value);
I am getting the following error when this is run:
Sequence contains more than one matching element
The Error message explains it all actually. Single() throw exception when there are more than one matching element within the condition.(or there is no element). You should use First() or FirstOrDefault()
DataRow resultRow = currencyAmounts.AsEnumerable().FirstOrDefault(r => ((decimal)r["OriginalAmount"]) == value);
you need First instead of Single
You can change the "Single()" with a "First()".
That's because single (from the microsoft page) "Returns the only element of a sequence, and throws an exception if there is not exactly one element in the sequence."
Single will throw an error if there are more or less than 1 element in the collection.
If you want the first, and are sure there is always at least one result, use
.Single();
If you are not sure there is always at least one result, use
.SingleOrDefault();

Does .Any with a predicate have to generate the resulting list before it looks for .Any

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.

LINQ returning IEnumerable<string> value in seeming charArray

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.

Return single column with Linq

public string GetNameBySSN(string SSN)
{
var results = from c in Clients
where c.ClientSSN.Equals(IRRCNumber)
select c.FirstName;
//return results.ToString();
}
I want to return a single column value with Linq and not sure if there is way to do a quick easy return. I commented section of code that I know does not return the value and if enumerate through collection I can get the value but wondering if a better way was around.
I do understand that Linq is Set based and that it has no upfront means know that the
result is set with a single member.
results.FirstOrDefault();
This way will not throw an exception, FYI. Where as #Mehrdad will.
return results.First().ToString();
Other possible variants:
.First() returns the first item and throws an exception if no item exists. It ignores all other items.
.FirstOrDefault() returns the first item if exists and returns the default value of the result type if nothing exists. It ignores all other items.
.Single() returns the only item if the collection contains exactly one item and throws an exception otherwise.
.SingleOrDefault() is like .Single() but returns the default value of the result type instead of throwing an exception.

Return one result from LINQ Query

if you have a select LINQ query that should only return one result, do you have to have a foreach loop to get the result?
Or is there a better way?
// Will return a default value if no object is found in the DB
db.Table.SingleOrDefault(x => x.something == someParameter);
or
// Will throw an exception if records are not found
db.Table.Single(x => x.something == someParameter);
Thanks to Mehrdad for the comment...both lines have been updated.
If it's possible that your query could result in more than one record being returned, then (as mentioned in comments) Single() and SingleOrDefault() are the wrong methods to call. You would keep the same syntax, but call First() and FirstOrDefault() respectively.
var myLinqObj = db.MyObjects.Take(1).SingleOrDefault();
You can use either First or Single.
First returns the first row, whether there are multiple rows or just the one.
Single expects only one row to be returned, and throws an exception if there are multiple rows.
Single is therefore potentially a better choice if you expect to only have one row, so that you'll see the problem immediately and can troubleshoot it.
You can just use .First() or .FirstOrDefault() like so:
Foo foo = query.Select(a => a.Name == "foo").FirstOrDefault();
From all the have have said my addition is the use the Value property after you have a single element if you are using LINQ-to-XML.
And the Select new { *cols* } if it a list or array or table.
Example.
... select new {c.Name, c.Value};
This tip is to enable you get the values.

Categories