AsEnumerable and how it affects a yield and a SqlDataReader
-
08-12-2019 - |
Question
I'm trying to understand what the affect of AsEnumerable()
has over my data when iterating over it. I have a mock in-memory list. If I foreach
over it with first calling ToList()
, this forces evaluation and my printout looks like this (see code at the bottom of this post to explain output):
entering da
yield return
yield return
yield return
exiting da
doing something to aaron
doing something to jeremy
doing something to brendan
All makes sense. The ToList()
forces the yields in the repository to execute first into a list, then we get our foreach
iteration. All good so far.
When I do the same except use AsEnumerable()
, based on what I've read regarding IQueryable
(I understand this isn't IQueryable
), I would have thought this also forces evaluation, but it does not. It looks like this:
entering da
yield return
doing something to aaron
yield return
doing something to jeremy
yield return
doing something to brendan
exiting da
As it would if I never even called AsEnumerable()
, so my question is:
Why does
AsEnumerable
behave differently for an in memory collection vs linq to sql and itsIQueryable
return type?How would all this change when my repository is changed to using a
SqlDataReader
and doing ayield return
inside of the reader (whilst callingRead()
method). Would the rows coming from SqlServer that are buffered in the clients network buffer be fully evaluated before executing theforeach
(normally ayield
here will cause a "pause" in the repo while each row is processed by theforeach
block. I know if I callToList()
first in this case, I can force evaluation of theSqlDataReader
, so doesAsEnumerable
do the same here?
Note: I am not interested in whether putting yield into a SqlDataReader
is a good idea, given it might hold the connection open, I've beaten this topic to death already :)
Here is my test code:
public class TestClient
{
public void Execute()
{
var data = MockRepo.GetData();
foreach (var p in data.AsEnumerable()) //or .ToList()
{
Console.WriteLine("doing something to {0}", p.Name);
}
Console.ReadKey();
}
}
public class Person
{
public Person(string name)
{
Name = name;
}
public string Name { get; set; }
}
public class MockRepo
{
private static readonly List<Person> items = new List<Person>(3)
{
new Person("aaron"),
new Person("jeremy"),
new Person("brendan")
};
public static IEnumerable<Person> GetData()
{
Console.WriteLine("entering da");
var enumerator = items.GetEnumerator();
while (enumerator.MoveNext())
{
Console.WriteLine("yield return");
yield return enumerator.Current;
}
Console.WriteLine("exiting da");
}
}
Solution
AsEnumerable
does nothing except change the expression type to IEnumerable<T>
. When it's used in a query like this:
var query = db.Customers
.Where(x => x.Foo)
.AsEnumerable()
.Where(x => x.Bar);
... that just means you'll use Queryable.Where
for the first predicate (so that's converted to SQL), and Enumerable.Where
for the second predicate (so that's executed in your .NET code).
It doesn't force evaluation. It doesn't do anything. It doesn't even check whether it's called on null
.
See my Edulinq blog post on AsEnumerable
for more information.
OTHER TIPS
@Jon Skeet has already posted what AsEnumerable()
does - it just changes the compile time type. But why would you use it?
Essentially by changing the expression from an IQueryable
to an IEnumerable
you can now use Linq to Objects (instead of the IQueryable implementation by your database provider) without any restriction - there does not have to be an equivalent method on the database side, so you can freely perform object transformation, remote calls (if required) or any sort of string manipulation.
That said you will want to do all the filtering you can while you are still working on the database (IQueryable
) - otherwise you would be bringing all these rows into memory which will cost you - and only then use AsEnumerable()
to do your final transformations afterwards.
According to the MSDN documentation:
The AsEnumerable(Of TSource)(IEnumerable(Of TSource)) method has no effect other than to change the compile-time type of source from a type that implements IEnumerable(Of T) to IEnumerable(Of T) itself.
It should not cause any evaluation, just hint that you want to use IEnumerable methods vs. some other implementation (IQueryable, etc.).