将IEnumerable投影到计划的IQueryable中,向数据库发出N个请求

时间:2019-04-19 01:17:16

标签: c# linq asp.net-core entity-framework-core odata

我正在创建一个Asp.net Core Api,并且我需要返回Controller的Action之一,以返回DTO的IQueryable,但是其中一个属性是另一个DTO的IEnumerable,其关系是: EF的数据库模型。 例如:

    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; }
    }

还有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; }
    }

这只是一个简单的示例,因为在我的应用程序中,每个表上都有很多字段,并且我无法将所有内容公开给前端应用程序,这就是为什么我使用DTO的原因。

我正在使用“选择”将每个元素投影到DTO,那里没有问题,因为我可以在ASP.NET Core Web Server输出上看到系统仅向数据库发出一个请求(获取客户),但是当我尝试在CustomerDTO中投影OrdersDTO时,问题就来了。例如,如果我有100个客户,EF将向数据库发出101个请求。 (1个获得客户,100个获得每个客户的订单)

   [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
           })
         });
      }

如果在使用Select投影元素之前调用ToList(),它将仅向数据库发出一个请求(如预期),但是由于我使用的是OData,因此我需要返回一个IQueryable,以便前端应用程序可以直接执行查询,即使只是DTO

我已经尝试过像这样

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

它部分解决了问题,因为如果在100个客户中只有50个有订单,EF只会向数据库发出50个请求。

我想知道是否有解决此问题的方法,因为我不希望应用程序每次有用户调用API的此端点时都对数据库进行数百次查询。

1 个答案:

答案 0 :(得分:2)

投影内部集合时,您需要添加ToList()

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

首先,因为CustomerDTO.Orders属性类型为List<OrderDTO>,所以代码不会对此进行编译。

但是即使不是(假设它是IEnumerable<OrderDTO>),您仍然需要ToList才能获得EF Core 2.1引入的Optimization of correlated subqueries

  

我们改进了查询转换,以避免在许多常见情况下执行“ N + 1”个SQL查询,在这种情况下,使用投影中的导航属性会导致将根查询中的数据与相关子查询中的数据连接在一起。优化需要缓冲子查询的结果,我们要求您修改查询以选择采用新行为。

请注意最后一句话-“我们要求,您将查询修改为选择加入新行为” 。然后文档包含一个示例,并继续:

  

通过在正确的位置添加ToList(),表示缓冲适用于订单,从而可以进行优化