0

I'm querying data from the database using Entity Framework Core using the following code:

CamicDbContext context = new CamicDbContext();

public override object Read(DataManagerRequest dm, string key = null)
{
    IQueryable<Country> query = context.Set<Country>();

    query = query.Where(x => x.IsDeleted == false);
    query = query.Where(x => x.Code == "PT");

    int count = query.Cast<Country>().Count();

    return dm.RequiresCounts ? new DataResult() { Result = query, Count = count } : (object)query;
}

When the query variable is declared, it seems that it is getting all items in the table (image 1), which seems wrong as it is inefficient with larger entities; therefore, I want to initialise the variable without getting data, build the query and then execute the query only when it reaches return query so the server will only query and return the items matching the filters.

I've tried multiple solutions. This one - Creating dynamic queries with Entity Framework - is close to what I want to achieve, but the code in it has the same effect as the one I've mentioned above. I'm I missing something to use deferred execution?

I executed

IQueryable<Country> query = context.Set<Country>();

The result view shows all 264 entries in the table:

Query initialisation result view

8
  • 5
    I believe the results view will load IQueryables when viewed in debug mode. If I'm not mistaken, during runtime it shouldn't query until you get the count. Commented Dec 30, 2021 at 14:18
  • Why do you have a Cast<Country>() call when you already have a IQueryable<Country> instance? Commented Dec 30, 2021 at 14:21
  • 1
    Declaring the variable query does not fetch the whole content of the table. The rows are only retrieved when the query is executed/iterated. So your code should already do what you are planning to do. If it doesn't, add a minimal reproducible example which shows the problem you have. Commented Dec 30, 2021 at 14:27
  • 1
    "The Result View shows..." - noticed the message "Expanding the Results View will enumerate the IEnumerable"? Enumerate means execute. Commented Dec 30, 2021 at 14:35
  • @Progman thanks for the heads up. Indeed the cast is not necessary. And thank you for the answer, I've missed that little information @IvanStoev. Commented Dec 30, 2021 at 14:37

3 Answers 3

4

When the query variable is declared, it seems that it is getting all items in the table (image 1),

It's not

I'm I missing something to use deferred execution?

You are

Here's a screenshot from a test app after I turn logging on. In the code of the test app I create a context, build a query, and run it.. A couple of times in fact..

enter image description here

x is the context. If I open the debugger and expand the Orders "table" in the debugger tooltip there's a message at the bottom saying that looking at the results will enumerate the enumerable. If I enumerate, then the SQL will be executed:

enter image description here

There aren't any clauses on this - we're enumerating Orders which represents the entire table, so the SQL run is effectively SELECT * FROM Orders. There isn't any data in my Orders test table, which is why we see "no results" after the enumeration is complete, but if there were 1000 orders the debugger tooltip would have 1000 items in


The next line of code runs a LINQ Where:

enter image description here

This hasn't actually executed any SQL, but it's supplied a clause that EF can translate to SQL, so if you take a look in the DebugView property of the IQueryable returned by the Where, you can see the SQL that EF will form (...WHERE Ref = '') when it's run.

Enumerating the queryable would actually run the SQL. Calling for a Results View in the debugger would enumerate, as would doing something like ToArray, ToList, ToDictionary (internally they all enumerate), or even a plain old foreach etc on your queryable. Whenever there's an enumeration, it triggers the running of the SQL.

If you use something like First, Count, Single then these trigger the execution of the SQL query also; they don't enumerate the entire set, they modify the SQL that is run but they do cause execution..

..which brings me to the point raised in the comments.

The code I've written enables EF to form a query with a WHERE clause, but then it asks the DB to execute it twice: once to get the Count (calling .Count causes a SELECT COUNT(*) ... WHERE ..) and then again to get the actual data items (calling ToList does a SELECT columns ... WHERE ...`):

enter image description here

Given that you have to retrieve e.g. 100 items during the enumeration it would make more sense to retrieve them all and then get the local count of them; if you've stashed them in a list then the list will be tracking the Count

Keep in mind that when you're working with EF's sets and you haven't done anything to enumerate them already, the queryable you;re passing around and calling operations on essentially represents an SQL (or a partly built one) and every time you do something to it that will deliver data, it'll run the SQL

Sign up to request clarification or add additional context in comments.

Comments

1

LINQ can execute in one of two following ways: immediate or deferred. Immediate execution means that the data source is read and the operation is performed at the point in the code where the query is declared. All the standard query operators that return a single, non-enumerable result execute immediately where as a Deferred execution basically means that the operation is not performed at the point in the code where the query is declared. The operation is performed only when the query variable is enumerated.

Expending and viewing the IQueryable when debugging will act as immediate Query Execution, hence bringing that query in memory. in a non debugging environment this won't happen and the query will act as deferred query execution, meaning that it will actually be executed once the Count() method will get called

More about classification of Standard Query Operators by Manner of Execution can be found here - https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/classification-of-standard-query-operators-by-manner-of-execution

1 Comment

Although the Linq query will be executed/resolved when Count is invoked, a count-query will also resolve to count in SQL, so it will not fetch and return all objects from the table AFAIK. (edit: oh, now I see Caius' answer, who indeed states it as such)
0

see https://weblogs.asp.net/zeeshanhirani/using-asqueryable-with-linq-to-objects-and-linq-to-sql

1.When you use AsQueryable operator then further transformations are allowed: filtering, sorting, or aggregating 2.The additional lambda statements are converted to Expression trees. I illustrate an expression filter using a lambda function. 3.If you use AsQueryable operator on a query that does not implement IQueryable and only implements IEnumerable< T >,than any transformations that you apply on the query would automatically fall back on IEnumerable specification. 4. IQueryable if deferred until a foreach is called and the expression tree is evaluated

 Product[] products =
                    {
                    new Product {ProductId=1, Name="Kayak",Category="Watersports",Price=275m, Company=new Company{ CompanyName="Abc Corp",States=new String[]{"Ut","Id" } }  },
                    new Product {ProductId=2, Name="Lifejacket", Category="Watersports",Price=48.95m, Company=new Company{ CompanyName="X Corp",States=new String[]{"Ca","Az" } }},
                    new Product {ProductId=3, Name="Soccer Ball", Category="Soccer",Price=19.50m, Company=new Company{ CompanyName="Y Corp",States=new String[]{"Tx","Wa" } }},
                    new Product {ProductId=4, Name="Corner Flag", Category="Soccer",Price=34.95m, Company=new Company{ CompanyName="Z Corp",States=new String[]{"Co","Wy" } }}
                     };
                  Transaction[] transactions =
                    {
                        new Transaction{ Name ="Bob Smith", ProductId=1, Cost=10.95M, Quantity=10},
                        new Transaction{ Name ="Dan Brown", ProductId=3, Cost=5.99M, Quantity=1}
                    };
    
    
    
                IQueryable<Product> query = products.AsQueryable<Product>();
    
                Expression<Func<Product, bool>> filter = x => x.Price > 100;
                 query = query.Where(filter);
    
                int count = query.Cast<Product>().Count();
    
                var result = query.Join(transactions,
                    product => new { product.ProductId },
                    transaction => new { transaction.ProductId },
                    (product, transaction) =>
                    new
                    {
                        CustomerName = transaction.Name,
                        ProductName = product.Name,
                        CompanyName = product.Company.CompanyName,
                        Price = product.Price
                    }) ;
    
                foreach (var item in result)
                {
                    _output.WriteLine($"Count: {count} Company: {item.CompanyName} Customer: {item.CustomerName} Product : {item.ProductName} Price : {String.Format("{0:C}", item.Price)}");
                }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.