What's the difference(s) between .ToList(), .AsEnumerable(), AsQueryable()?
Asked Answered
M

5

220

I know some differences of LINQ to Entities and LINQ to Objects which the first implements IQueryable and the second implements IEnumerable and my question scope is within EF 5.

My question is what's the technical difference(s) of those 3 methods? I see that in many situations all of them work. I also see using combinations of them like .ToList().AsQueryable().

  1. What do those methods mean, exactly?

  2. Is there any performance issue or something that would lead to the use of one over the other?

  3. Why would one use, for example, .ToList().AsQueryable() instead of .AsQueryable()?

Motherwell answered 31/7, 2013 at 10:56 Comment(1)
#3628925 and #1107302Tomtoma
E
452

There is a lot to say about this. Let me focus on AsEnumerable and AsQueryable and mention ToList() along the way.

What do these methods do?

AsEnumerable and AsQueryable cast or convert to IEnumerable or IQueryable, respectively. I say cast or convert with a reason:

  • When the source object already implements the target interface, the source object itself is returned but cast to the target interface. In other words: the type is not changed, but the compile-time type is.

  • When the source object does not implement the target interface, the source object is converted into an object that implements the target interface. So both the type and the compile-time type are changed.

Let me show this with some examples. I've got this little method that reports the compile-time type and the actual type of an object (courtesy Jon Skeet):

void ReportTypeProperties<T>(T obj)
{
    Console.WriteLine("Compile-time type: {0}", typeof(T).Name);
    Console.WriteLine("Actual type: {0}", obj.GetType().Name);
}

Let's try an arbitrary linq-to-sql Table<T>, which implements IQueryable:

ReportTypeProperties(context.Observations);
ReportTypeProperties(context.Observations.AsEnumerable());
ReportTypeProperties(context.Observations.AsQueryable());

The result:

Compile-time type: Table`1
Actual type: Table`1

Compile-time type: IEnumerable`1
Actual type: Table`1

Compile-time type: IQueryable`1
Actual type: Table`1

You see that the table class itself is always returned, but its representation changes.

Now an object that implements IEnumerable, not IQueryable:

var ints = new[] { 1, 2 };
ReportTypeProperties(ints);
ReportTypeProperties(ints.AsEnumerable());
ReportTypeProperties(ints.AsQueryable());

The results:

Compile-time type: Int32[]
Actual type: Int32[]

Compile-time type: IEnumerable`1
Actual type: Int32[]

Compile-time type: IQueryable`1
Actual type: EnumerableQuery`1

There it is. AsQueryable() has converted the array into an EnumerableQuery, which "represents an IEnumerable<T> collection as an IQueryable<T> data source." (MSDN).

What's the use?

AsEnumerable is frequently used to switch from any IQueryable implementation to LINQ to objects (L2O), mostly because the former does not support functions that L2O has. For more details see What is the effect of AsEnumerable() on a LINQ Entity?.

For example, in an Entity Framework query we can only use a restricted number of methods. So if, for example, we need to use one of our own methods in a query we would typically write something like

var query = context.Observations.Select(o => o.Id)
                   .AsEnumerable().Select(x => MySuperSmartMethod(x))

ToList – which converts an IEnumerable<T> to a List<T> – is often used for this purpose as well. The advantage of using AsEnumerable vs. ToList is that AsEnumerable does not execute the query. AsEnumerable preserves deferred execution and does not build an often useless intermediate list.

On the other hand, when forced execution of a LINQ query is desired, ToList can be a way to do that.

AsQueryable can be used to make an enumerable collection accept expressions in LINQ statements. See here for more details: Do i really need use AsQueryable() on collection?.

Note on substance abuse!

AsEnumerable works like a drug. It's a quick fix, but at a cost and it doesn't address the underlying problem.

In many Stack Overflow answers, I see people applying AsEnumerable to fix just about any problem with unsupported methods in LINQ expressions. But the price isn't always clear. For instance, if you do this:

context.MyLongWideTable // A table with many records and columns
       .Where(x => x.Type == "type")
       .Select(x => new { x.Name, x.CreateDate })

...everything is neatly translated into a SQL statement that filters (Where) and projects (Select). That is, both the length and the width, respectively, of the SQL result set are reduced.

Now suppose users only want to see the date part of CreateDate. In Entity Framework you'll quickly discover that...

.Select(x => new { x.Name, x.CreateDate.Date })

...is not supported (at the time of writing). Ah, fortunately there's the AsEnumerable fix:

context.MyLongWideTable.AsEnumerable()
       .Where(x => x.Type == "type")
       .Select(x => new { x.Name, x.CreateDate.Date })

Sure, it runs, probably. But it pulls the entire table into memory and then applies the filter and the projections. Well, most people are smart enough to do the Where first:

context.MyLongWideTable
       .Where(x => x.Type == "type").AsEnumerable()
       .Select(x => new { x.Name, x.CreateDate.Date })

But still all columns are fetched first and the projection is done in memory.

The real fix is:

context.MyLongWideTable
       .Where(x => x.Type == "type")
       .Select(x => new { x.Name, DbFunctions.TruncateTime(x.CreateDate) })

(But that requires just a little bit more knowledge...)

What do these methods NOT do?

Restore IQueryable capabilities

Now an important caveat. When you do

context.Observations.AsEnumerable()
                    .AsQueryable()

you will end up with the source object represented as IQueryable. (Because both methods only cast and don't convert).

But when you do

context.Observations.AsEnumerable().Select(x => x)
                    .AsQueryable()

what will the result be?

The Select produces a WhereSelectEnumerableIterator. This is an internal .Net class that implements IEnumerable, not IQueryable. So a conversion to another type has taken place and the subsequent AsQueryable can never return the original source anymore.

The implication of this is that using AsQueryable is not a way to magically inject a query provider with its specific features into an enumerable. Suppose you do

var query = context.Observations.Select(o => o.Id)
                   .AsEnumerable().Select(x => x.ToString())
                   .AsQueryable()
                   .Where(...)

The where condition will never be translated into SQL. AsEnumerable() followed by LINQ statements definitively cuts the connection with entity framework query provider.

I deliberately show this example because I've seen questions here where people for instance try to 'inject' Include capabilities into a collection by calling AsQueryable. It compiles and runs, but it does nothing because the underlying object does not have an Include implementation anymore.

Execute

Both AsQueryable and AsEnumerable don't execute (or enumerate) the source object. They only change their type or representation. Both involved interfaces, IQueryable and IEnumerable, are nothing but "an enumeration waiting to happen". They are not executed before they're forced to do so, for example, as mentioned above, by calling ToList().

That means that executing an IEnumerable obtained by calling AsEnumerable on an IQueryable object, will execute the underlying IQueryable. A subsequent execution of the IEnumerable will again execute the IQueryable. Which may be very expensive.

Specific Implementations

So far, this was only about the Queryable.AsQueryable and Enumerable.AsEnumerable extension methods. But of course anybody can write instance methods or extension methods with the same names (and functions).

In fact, a common example of a specific AsEnumerable extension method is DataTableExtensions.AsEnumerable. DataTable does not implement IQueryable or IEnumerable, so the regular extension methods don't apply.

Explorer answered 1/8, 2013 at 14:3 Comment(4)
Thanks for the answer, can you please share your answer to 3rd question of OP - 3. Why would one use, for example, .ToList().AsQueryable() instead of .AsQueryable()??Tibia
@ikram I can't think of anything where that would be useful. As I explained, applying AsQueryable() is often based on misconception. But I'll let is simmer in the back of my head for a while and see if I can add some more coverage on that question.Explorer
@Tibia the reason I've seen to call a ToList before querying is because of problems on the underlying DB object. For example a db VIEW with several joins where a certain Column that is calculated on the view for example concat(t1.colA, t2.colB, t2.colC) as MyVeryNeetColumn (i know its a stupid example, but could be anything). When you do a Where on that column in it will have horrible times because the db needs to calculate all vals to perform the Where. In code you can toList the records to memory and do the Where on the result, sometimes memory usage vs wait time is a valid reason to choosePastiche
I have to situations: 1): IEnumerable<T> resultList= queryableObject.AsEnumerable(); //execute database query var test= resultList.ToList() //execute database query AGAIN 2)IEnumerable<T> resultList= queryableObject.ToList(); //execute database query var test= resultList.ToList(); //NOT executing database query AGAIN It's happens because in situation 1 the type isn't actually changed yes?Jahdai
M
70

ToList()

  • Execute the query immediately

AsEnumerable()

  • lazy (execute the query later)
  • Parameter: Func<TSource, bool>
  • Load EVERY record into application memory, and then handle/filter them. (e.g. Where/Take/Skip, it will select * from table1, into the memory, then select the first X elements) (In this case, what it did: Linq-to-SQL + Linq-to-Object)

AsQueryable()

  • lazy (execute the query later)
  • Parameter: Expression<Func<TSource, bool>>
  • Convert Expression into T-SQL (with the specific provider), query remotely and load result to your application memory.
  • That’s why DbSet (in Entity Framework) also inherits IQueryable to get the efficient query.
  • Do not load every record, e.g. if Take(5), it will generate select top 5 * SQL in the background. This means this type is more friendly to SQL Database, and that is why this type usually has higher performance and is recommended when dealing with a database.
  • So AsQueryable() usually works much faster than AsEnumerable() as it generate T-SQL at first, which includes all your where conditions in your Linq.
Mcandrew answered 28/9, 2015 at 16:29 Comment(0)
M
14

ToList() will being everything in memory and then you will be working on it. so, ToList().where ( apply some filter ) is executed locally. AsQueryable() will execute everything remotely i.e. a filter on it is sent to the database for applying. Queryable doesn't do anything til you execute it. ToList, however executes immediately.

Also, look at this answer Why use AsQueryable() instead of List()?.

EDIT : Also, in your case once you do ToList() then every subsequent operation is local including AsQueryable(). You can't switch to remote once you start executing locally. Hope this makes it a little bit more clearer.

Maitland answered 31/7, 2013 at 11:7 Comment(2)
"AsQueryable() will execute everything remotely" Only if the enumerable already is queryable. Otherwise, that isn't possible, and everything still runs locally. The question has "....ToList().AsQueryable()", which could use some clarification in your answer, IMO.Thacher
I understand the implication of IQueryable<T> versus IEnumerable<T> in Linq-to-SQL. What I am unclear of is when and where to use them. Can I just write var customer = context.Customers.Where(c => c.Id == 123) and assume it's IQueryable<Customer> in EF? Does var tell the compiler to use IEnumerable<T>? I read the DbSet<T> implements IQueryable<T> so I assume var is IQueryable<T>. Then what's the point of adding AsQueryable() to the end of the query above which converts an object that already implements IQueryable<T> to IQueryable<T>. ThanksSigrid
M
2

Encountered a bad performance on below code.

void DoSomething<T>(IEnumerable<T> objects){
    var single = objects.First(); //load everything into memory before .First()
    ...
}

Fixed with

void DoSomething<T>(IEnumerable<T> objects){
    T single;
    if (objects is IQueryable<T>)
        single = objects.AsQueryable().First(); // SELECT TOP (1) ... is used
    else
        single = objects.First();

}

For an IQueryable, stay in IQueryable when possible, try not be used like IEnumerable.

Update. It can be further simplified in one expression, thanks Gert Arnold.

T single =  objects is IQueryable<T> q? 
                    q.First(): 
                    objects.First();
Marley answered 17/7, 2019 at 19:6 Comment(0)
M
0
// Main use of AsQueryable operator is in unit testing to mock a queryable data 
//source using an in memroy data source

    public class Employee
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public string Gender { set; get; }
        public int Salary { set; get; }
    }

    public class Program
    {
        public static void Main()
        {
            List<Employee> employees = new List<Employee>
                {
                    new Employee{Id=101,Name="Tom",Gender="Male",Salary =  800},
                    new Employee{Id=102,Name="Marry",Gender="Female",Salary = 900},
                    new Employee{Id=103,Name="Pam",Gender="Female",Salary = 800},
                    new Employee{Id=104,Name="Jhon",Gender="Male",Salary = 800},
                    new Employee{Id=105,Name="Jhon",Gender="Male",Salary = 800},
                    new Employee{Id=106,Name="Brin",Gender="Male",Salary = 700},
                    new Employee{Id=107,Name="Jade",Gender="Male",Salary = 750},
                    new Employee{Id=108,Name="Ron",Gender="Male",Salary = 850},
                    new Employee{Id=109,Name="Rob",Gender="Male",Salary = 950},
                    new Employee{Id=110,Name="Alex",Gender="Male",Salary = 750},
                    new Employee{Id=111,Name="Susan",Gender="Female",Salary = 860}
                };

        //this is in-memory data source but assume that we have retrived list of employee from data base

        var result = employees
            .Where(x => x.Gender == "Male")          //<-- All conditions applied on SQL server side
            .OrderByDescending(x => x.Salary)
            .Take(5);

        var result1 = employees
            .AsEnumerable()
            .Where(x => x.Gender == "Male")          //<-- Get all Data From SQL server and then apply all condtions in memory i.e Where,OrderByDescending &Take
            .OrderByDescending(x => x.Salary)
            .Take(5);



        var result2 = employees
            .Where(x => x.Gender == "Male")          //<-- Get filtered Data (only male employee) from SQL server and then apply OrderByDescending &Take
            .AsEnumerable()
            .OrderByDescending(x => x.Salary)
            .Take(5);


        //1. Create a table in sql server
        //2. Create Linq To SQL mapping 
        //3. Retrive data using Linq To SQL  
        //4. run sql profiler(Event selection uncheck all audit events) and execute above query one by one you will how data retrived from sql server


        //So in short, use AsEnumerable operator to move query processing to the client side 

    }
Mceachern answered 29/5, 2023 at 1:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.