【问题标题】:Projecting an IEnumerable inside an Projected IQueryable making N Requests to the Database在 Projected IQueryable 中投影一个 IEnumerable,向数据库发出 N 个请求
【发布时间】:2019-04-19 01:17:15
【问题描述】:

我正在制作一个 Asp.net Core Api 和一个控制器的操作,我需要返回一个 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 的原因。

我正在使用 Select 将每个元素投影到 DTO,那里没有问题,因为我可以在 ASP.NET Core Web 服务器输出上看到系统只向数据库发出一个请求(以获取客户),但是当我尝试将 OrdersDTO 投影到 CustomerDTO 中时,问题就来了。例如,如果我有 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(),它将只向数据库发出一个请求(如预期的那样),但我需要返回一个 IQueryable,因为我使用的是 OData,以便前端应用程序可以直接对数据库执行查询,即使只是一个 DTO

我已经试过这样放

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

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

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

【问题讨论】:

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


    【解决方案1】:

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

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

    首先因为CustomerDTO.Orders的属性类型是List&lt;OrderDTO&gt;,所以代码不会编译没有它。

    但即使不是(假设是 IEnumerable&lt;OrderDTO&gt;),您仍然需要 ToList 才能引入 EF Core 2.1 Optimization of correlated subqueries

    我们改进了查询翻译,以避免在许多常见场景中执行“N + 1”SQL 查询,在这些场景中,在投影中使用导航属性会导致来自根查询的数据与来自相关子查询的数据相结合。优化需要缓冲来自子查询的结果,我们要求您修改查询以选择加入新行为。

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

    通过在正确的位置包含ToList(),您表明缓冲适合订单,从而启用优化

    【讨论】:

    • 非常感谢!!它解决了问题!几天来我一直在尝试寻找解决方案,但没有注意到对 EF Core 2.1 的这一新改进实际上我有一个错字,实际上是IEnumerable&lt;OrderDTO&gt;
    猜你喜欢
    • 1970-01-01
    • 2019-12-11
    • 1970-01-01
    • 2019-03-09
    • 2019-05-31
    • 2022-11-10
    • 1970-01-01
    • 2014-05-13
    • 1970-01-01
    相关资源
    最近更新 更多