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:

  1. Why does AsEnumerable behave differently for an in memory collection vs linq to sql and its IQueryable return type?

  2. How would all this change when my repository is changed to using a SqlDataReader and doing a yield return inside of the reader (whilst calling Read() method). Would the rows coming from SqlServer that are buffered in the clients network buffer be fully evaluated before executing the foreach (normally a yield here will cause a "pause" in the repo while each row is processed by the foreach block. I know if I call ToList() first in this case, I can force evaluation of the SqlDataReader, so does AsEnumerable 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");
        }
    }
Was it helpful?

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.).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top