2

I've read an article that in Entity Framework, the query will be sent to database after we call .ToList(), Single(), or First()

I have thousands of data so rather than load all the data I'd like to return data in paged. So I'm using PagedList to create paging in MVC. If it doesn't wrong when we called for example products.ToPagedList(pageNumber, 10), it will take only 10 records of data, not the whole data. Am I right?

Next, I'm using automapper to map from entities to viewmodel.

List<ProductViewModel> productsVM = Mapper.Map<List<Product>, List<ProductViewModel>>(products);
return productsVM.ToPagedList(pageNumber, 10);

As you can see in the snippet code above, does it take only 10 records before called .ToPagedList()? If when we do mapping, it will call .ToList() inside, I think it will call all of the data from the database then return 10 records. How to trace it?

Willy
  • 1,689
  • 7
  • 36
  • 79

2 Answers2

0

The easiest way to see what is going on at database level is to use Sql Server Profiler. Then you will be able to see the sql queries that the entity framework is executing.

If you are using Sql Express then you can use Sql Express Profiler to do the same thing.

Colin Bacon
  • 15,436
  • 7
  • 52
  • 72
0

No, it doesn't take 10 records before paged list. The way your code is shown, AutoMapper will cause a deferred execution of the query, before it reaches paged list, which means it will return all data (let's suppose, 1000 records). Then PagedList will properly retrieve 10 of the already materialized List, and recognize the total amount of record was 1000.

I think you want to filter 10 in database, which has better performance, so you should use PagedList in the IQueryable of your database entities like this:

List<Product> filteredProducts = dbContext.Products.OrderBy(p => p.ProductId).ToPagedList(pageNumber, 10);
return Mapper.Map<List<Product>, List<ProductViewModel>>(filteredProducts);

The OrderBy is mandatory for PagedList.


BE CAREFUL WITH AUTOMAPPER

Consider the following scenario. What if your Product entity had a child relationship with ProductReview (a ICollection<ProductReview>) like this:

public class ProductReview
{
    public int ProductId { get; set; }
    public string Description { get; set; }
    public int ReviewerId { get; set; }
    public double Score { get; set; }
}

public class Product
{
    public int ProductId { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public virtual ICollection<ProductReview> Reviews { get; set; }
}

...and your ProductViewModel had an int property ReviewsCount to show in your view?

When Automapper would map and transform your entities into view model, it would access the Reviews property of each Product in the List (let's suppose, 10 in your case), one by one, and get the Reviews.Count() to fill ReviewsCount in your ProductViewModel.

Considering my example, where I never eager loaded Reviews of products, if Lazy Load was on, AutoMapper would execute ten queries (one per product) to count Reviews. Count is a fast operation and ten products are just a few. but if instead of count you were actually mapping the ProductReview to a ProductReviewViewModel, this would be kinda heavy. If Lazy Load was turned off, we would get an exception, since Reviews would be null.

One possible solution, is to eager load all child you might need during mapping, like this:

List<Product> filteredProducts = dbContext.Products.Include("Reviews").OrderBy(p => p.ProductId).ToPagedList(pageNumber, 10);
return Mapper.Map<List<Product>, List<ProductViewModel>>(filteredProducts);

...so 10 products and their reviews would be retrieved in just one query, and no other queries would be executed by AutoMapper.

But.......I just need a count, do I really need to retrieve ALL Reviews just to avoid multiple queries?

Isn't it also heavy to load all reviews and all their expensive fields like Description which may have thousands of characters???

Yes, absolutely. Avoid mixing PagedList with AutoMapper for these scenarios.

Just do a projection like this:

        List<Product> filteredProducts = dbContext.Products
            .Select(p => new ProductViewModel
            {
                ProductId = p.ProductId,
                ProductName = p.Name,
                ProductDescription = p.Description,
                ReviewsCount = p.Reviews.Count(),
                ScoreAverage = p.Reviews.Select(r => r.Score).DefaultIfEmpty().Average()
            })
            .OrderBy(p => p.ProductId).ToPagedList(pageNumber, 10);

Now you are loading your 10 products, projecting them into ProductViewModel, calculating the Reviews count and score average, without retrieving all Reviews from database.

Of course there are scenarios where you might really need all child entities loaded/materialized, but other than that, projection ftw.

You can also put the Select() part inside an extension class, and encapsulate all your projections in extension methods, so you can reuse them like you would to with AutoMapper.

I'm not saying AutoMapper is evil and you shouldn't use it, I use it myself in some situations, you just need to use it when it's appropriate.


EDIT: AUTOMAPPER DOES SUPPORT PROJECTION

I found this question where @GertArnold explains the following about AutoMapper:

...the code base which added support for projections that get translated into expressions and, finally, SQL

So be happy, just follow his suggestion.

Community
  • 1
  • 1
Alisson Reinaldo Silva
  • 10,009
  • 5
  • 65
  • 83