【问题标题】:EntityFramework Core Project Joined Rows For Skip/Take PaginationEntityFramework 核心项目加入行以进行跳过/分页
【发布时间】:2021-08-02 05:30:32
【问题描述】:

使用 Asp.Net 3.1 Core EntityFramework Core LINQ,假设我有一个 Order 表和一个 Customer 表:

public class Order
{
     public long Id { get; set; }
     public string CustomerId { get; set; }
     public int Total {get; set;}
     public virtual Customer Customer{ get; set; }
}

public class Customer : ApplicationUser
{
   public long Id {get; set;}
   public virtual ICollection<Order> Orders { get; set; }
}

最终,我想返回宇宙中每个客户的列表,即使他们没有订单(左外?),但我也希望每个订单都有一行。所以像:

Customer    Order    Total
--------    -----    -----
1           null     null
2           100      5
2           101      199
3           null     null
4           200      299
4           201      399

我遇到的复杂情况是我需要在服务器上执行此操作,因为我需要使用 skip/take 对这些数据进行分页。直接使用Context.Customer.Include(x =&gt; x.Order) 不会以我需要它们的方式投影行,以便使用skip/take 进行分页,而且我被困在语法上。

这在直接 LINQ 中是否可行?如果是这样,LINQ 会是什么样子?

提前致谢!

【问题讨论】:

  • 您到底想如何分页?您是说要基于数据行进行分页,即使这涉及将单个客户拆分为两个页面?
  • 是的,正是@BenM
  • 发布的模型中似乎存在一些类型不匹配 - stringOrder 中输入CustomerId 与在longCustomer 中输入Id,因此CustomerId 可以' t 是 FK,除非 Id 不是 PK 或关系已配置为使用其他一些 Customer 属性作为备用键。你能澄清一下吗?因为使用正确的模型进行左外连接非常容易 - 您需要考虑的是在投影中一些不可为空的字段变为可空。

标签: c# entity-framework entity-framework-core linq-to-entities ef-core-3.1


【解决方案1】:

您正在寻找使用 LINQ 查询语法的查询是这样的

var query =
    from c in context.Customers
    from o in c.Orders.DefaultIfEmpty()
    select new
    {
        CustomerId = c.Id,
        OrderId = (long?)o.Id,
        Total = (int?)o.Total
    };

一些注意事项。

首先,DefaultIfEmpty() 是产生左外连接的原因。如果没有它,它将被视为内部连接。

其次,由于现在Order 数据来自左外连接的(可选)右侧,您需要考虑到它可能是null。在 LINQ to Object 中,需要使用带有null 检查的条件运算符或空合并运算符。在 LINQ to Entities 中,这由 SQL 自然地处理,但您需要将不可为空字段的结果类型更改为它们的可空等效项。如上所示,在匿名投影中是通过显式转换实现的。

最后,为什么要查询语法?当然,它可以用方法语法编写(SelectMany,如 Steve Py 的回答),但由于 EF Core 团队似乎正在测试编译器生成的 LINQ 结构,如果你使用“错误”重载,你很容易遇到 EF Core 错误/ 图案。这里的“错误”并不是真正的错误,只是 EF Core 翻译器没有考虑到的东西。这里的“正确”是将SelectMany 重载与结果选择器一起使用:

var query = context.Customers
    .SelectMany(c => c.Orders.DefaultIfEmpty(), (c, o) => new 
    {
        CustomerId = c.Id,
        OrderId = (long?)o.Id,
        Total = (int?)o.Total
    });

使用查询语法,您就没有这样的问题。

【讨论】:

  • 这个答案确实帮助我理解了 LINQ Query 的“左连接”实现。非常感谢您不仅写了一个答案,而且还很好地解释了它:)
【解决方案2】:

这是可能的,但是根据我的测试,它似乎在 EF Core 3.1 中不起作用。

通常你可以这样做:

var results = context.Customers
    .SelectMany(c => c.Orders.DefaultIfEmpty()
        .Select(o => new { c.CustomerId, o.OrderId, o.Total }) 
    );

然后从那里使用分页 /w .Skip.Take

以上内容适用于 EF6,但对于 EF Core 3.1 (3.1.15),c.CustomerId 由于某种原因返回为 #null。似乎内部Select EF Core 不能/不会解析回外部SelectMany 客户参考。我在 EF Core 中看到了一些关于 DefaultIfEmpty 实现问题的参考资料。似乎是核心团队一直忽略的另一个功能。

我已发布此信息,以防有人知道该行为的解释或解决方法,或者可以验证 EF Core 5 是否仍然如此。

我可以使用 EF Core 3.1 做的事情要丑得多。它需要为返回值定义一个类型,您可能已经拥有或没有:

[Serializable]
public class OrderData 
{
    public int CustomerId { get; set; }
    public int? OrderId { get; set; }
    public int? Total { get; set; }
}

var results = context.Customers
    .Where(c => !c.Orders.Any())
    .Select(c => new OrderData { CustomerId = c.CustomerId, OrderId = null, Total = null })
    .Union(context.Customers
        .SelectMany(c => c.Orders.Select(o => new OrderData
        {
            CustomerId = c.CustomerId,
            OrderId = o.OrderId,
            Total = o.Total
        })));

有趣的是需要返回对象类型,因为它不会联合匿名类型(预期的),尽管它似乎需要您显式初始化每个空属性,否则您会得到一个投影异常。

您可以从那里订购结果(在联合之后)并应用分页。

【讨论】:

    【解决方案3】:

    您想要的是左外连接。 Microsoft 在https://docs.microsoft.com/en-us/dotnet/csharp/linq/perform-left-outer-joins 上提供了有关在 LINQ 中执行左外连接的信息。

    适应您的代码如下所示:

    from customer in context.Customers
      join order in context.Orders on customer equals order.Customer into gj
      from suborder in gj.DefaultIfEmpty()
      select new { CustomerId = customer.Id, OrderId = suborder?.Id, Total = suborder?.Total };
    

    从那里你可以应用分页。

    如果您更喜欢使用 lambda 和扩展方法,请参阅 How do you perform a left outer join using linq extension methods

    【讨论】:

    • 这是否与下面@Steve Py 的答案中提出的第一个解决方案基本相同,它不再适用于 EF Core?
    • 如果您曾经针对IQueryable 使用过LINQ,您就会知道表达式树(仍然)不支持?.,因此您只会遇到编译时错误。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-13
    • 1970-01-01
    相关资源
    最近更新 更多