Projecting an IEnumerable inside an Projected IQueryable making N Requests to the Database

若如初见. 提交于 2019-12-24 08:04:09

问题


I'm making an Asp.net Core Api and one of the Actions of the Controller i need to return an IQueryable of a DTO, but one of the properties is an IEnumerable of another DTO in a relationship one to many in the database model of EF. For example:

    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public DateTime Birthday { get; set; }
        public List<Order> Orders { get; set; }
    }

    public class Order
    {
        public int OrderNumber { get; set; }
        public Customer Customer { get; set; }
    }

And the DTO

    public class CustomerDTO
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public IEnumerable<OrderDTO> Orders { get; set; }
    }

    public class OrderDTO
    {
        public int OrderNumber { get; set; }
    }

This is just a simple example because in my application there is alot more fields on each table and i cannot expose everything to the frontend application, that's why i'm using DTOs.

I'm using the Select to Project each element to the DTO, there is no problem there because i can see on the ASP.NET Core Web Server output that the system is only making one request to the database (to get the Customers), but the problem comes when i try to project the OrdersDTO inside the CustomerDTO. What's happening is for example if i have 100 customers the EF will make 101 requests to the database. (1 to get the Customers and 100 to get the Orders for each customer)

   [HttpGet]
   [EnableQuery]
   public IEnumerable<CustomerDTO> Get()
      {
        return context.Customer
        .Select(s => new CustomerDTO
        {
           Id = s.Id,
           Name = s.Name,
           Orders = s.Orders.Select(so => new OrderDTO
           {
              OrderNumber = so.OrderNumber
           })
         });
      }

If i call ToList() before i project the elements using Select it will make only one request to the Database (as intended), but i need to return an IQueryable because i'm using OData, so that the frontend application can execute queries directly to the database even if is just a DTO

I already tried putting like this

     Orders = s.Orders.Any() ? s.Orders.Select(so => new OrderDTO
       {
         OrderNumber = so.OrderNumber
       }) : new List<OrderDTO>()

It solved the problem partially because if out of the 100 customers theres is only 50 that have orders the EF will only make 50 requests to the Database.

I would like to know if there is a solution to this problem because i don't want the application doing hundreds of queries to the database each time some user calls this endpoint of the API.


回答1:


You need to add ToList() when projecting the inner collection.

       Orders = s.Orders.Select(so => new OrderDTO
       {
          OrderNumber = so.OrderNumber
       }).ToList() // <--

First because CustomerDTO.Orders property type is List<OrderDTO>, so the code does not compile w/o that.

But even it wasn't (let say it's IEnumerable<OrderDTO>), you still need ToList in order to get EF Core 2.1 introduced Optimization of correlated subqueries :

We have improved our query translation to avoid executing "N + 1" SQL queries in many common scenarios in which the usage of a navigation property in the projection leads to joining data from the root query with data from a correlated subquery. The optimization requires buffering the results from the subquery, and we require that you modify the query to opt-in the new behavior.

Note the last sentence - "we require that you modify the query to opt-in the new behavior". Then documentation contains an example and continues with:

By including ToList() in the right place, you indicate that buffering is appropriate for the Orders, which enable the optimization



来源:https://stackoverflow.com/questions/55755457/projecting-an-ienumerable-inside-an-projected-iqueryable-making-n-requests-to-th

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!