Monday, July 21, 2008

LINQ query to pull a single record from a collection still requires enumercation over results

I wrote a LINQ query in Visual Basic .Net 2008 in order to pull a single record from a Generic List collection. The intent was to use LINQ as an easier alternative to writing a Predicate function for the .Find method of the collection. The code is below. I made what I believe will be a common LINQ mistake. Below is the explanation of the errors I saw, the code that produced those errors, and then the proper way to do it.

A symptom of the problem is that the LINQ query returned a SelectIterator object and in order to see the string I had to enumerate (For Each) over the result in order to obtain the data. I knew that the WHERE condition was sufficient to return only a single result, but LINQ did not so it returned a SelectIterator to be enable me to enumerate through the result set.

If I tried to assign the LINQ result directly to a string, I get the error message "Conversion from type 'd__d(Of MyDataType,String)' to type 'String' is not valid.".

The incorrect code is:

Dim Result as string = string.empty
Dim Filename = From item In myCollection _
Where item.key.ToLower = MenuPath.ToLower _
Select item.MyFilename
For Each s As String In Filename : Result = s : Next
Console.WriteLine("Returned String : " + s)


The correct code is achieved by putting parenthesis around the LINQ query and using the .Single method to inform LINQ that we are expecting a single-value result. The corrected code is :


Dim Result as string = string.empty
Dim Filename = (From item In myCollection _
Where item.key.ToLower = MenuPath.ToLower _
Select item.MyFilename).Single
Console.WriteLine("Returned String : " + Filename)


A special thank you to Bill Wagner of SRT Solutions for his solution to the error in the first listing when I initially blogged it.

Hope that helps.

Joe Kunk
Okemos, MI

1 comment:

Bill Wagner said...

Joe,

You can use Enumerable.Single() (if you know you want exactly one record), or Enumerable.First() to return the first item in the sequence. That's easier than the explicit ForEach