EntityFramework 核心项目为 Skip/Take 分页加入行
EntityFramework Core Project Joined Rows For Skip/Take Pagination
使用 Asp.Net 3.1 Core EntityFramework Core LINQ,假设我有一个订单 table 和一个客户 table:
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; }
}
最终,我想要 return 宇宙中每个客户的列表,即使他们没有订单(左外?),但我还希望每个订单都有一行。所以像:
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 => x.Order)
不会按照我需要的方式投影行,以便使用 skip/take
进行分页,我被语法困住了。
在直接 LINQ 中这可能吗?如果是这样,LINQ 会是什么样子?
提前致谢!
这是可能的,但是根据我的测试,它似乎无法在 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
由于某种原因 return 编辑为 #null。似乎内部 Select
EF Core 无法/不会解析回外部 SelectMany
客户引用。我看到了一些关于 EF Core 中 DefaultIfEmpty
实现问题的参考。似乎是核心团队一直忽视的另一个功能。
我已经发布了这个,以防有人知道该行为的解释或解决方法,或者可以验证 EF Core 5 是否仍然如此。
我可以使用 EF Core 3.1 得到的东西更难看。它需要 return 值的定义类型,您可能已经拥有或没有:
[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
})));
有趣的是 return 对象类型是必需的,因为它不会联合匿名类型(预期),尽管它似乎确实要求您显式初始化每个空 属性 或你得到一个投影异常。
从那里您可以对结果进行排序(合并后)并应用分页。
你想要的是左外连接。 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
您使用 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
检查的条件运算符或 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
});
使用查询语法就不会出现此类问题。
使用 Asp.Net 3.1 Core EntityFramework Core LINQ,假设我有一个订单 table 和一个客户 table:
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; }
}
最终,我想要 return 宇宙中每个客户的列表,即使他们没有订单(左外?),但我还希望每个订单都有一行。所以像:
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 => x.Order)
不会按照我需要的方式投影行,以便使用 skip/take
进行分页,我被语法困住了。
在直接 LINQ 中这可能吗?如果是这样,LINQ 会是什么样子?
提前致谢!
这是可能的,但是根据我的测试,它似乎无法在 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
由于某种原因 return 编辑为 #null。似乎内部 Select
EF Core 无法/不会解析回外部 SelectMany
客户引用。我看到了一些关于 EF Core 中 DefaultIfEmpty
实现问题的参考。似乎是核心团队一直忽视的另一个功能。
我已经发布了这个,以防有人知道该行为的解释或解决方法,或者可以验证 EF Core 5 是否仍然如此。
我可以使用 EF Core 3.1 得到的东西更难看。它需要 return 值的定义类型,您可能已经拥有或没有:
[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
})));
有趣的是 return 对象类型是必需的,因为它不会联合匿名类型(预期),尽管它似乎确实要求您显式初始化每个空 属性 或你得到一个投影异常。
从那里您可以对结果进行排序(合并后)并应用分页。
你想要的是左外连接。 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
您使用 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
检查的条件运算符或 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
});
使用查询语法就不会出现此类问题。