引言
大家好,我是刚子。
实际业务中,左连接(Left Join)远比内连接常见;而 string.Join 和 EF Core 中的 Join 又是两个极易被滥用的高频场景。本文为你提供这三者的实战解决方案。
一、LINQ to Objects 中的 Left Join 写法
Join 本身只支持内连接,要实现左连接必须使用 GroupJoin 配合 SelectMany 和 DefaultIfEmpty。
标准模版:
var leftJoin = customers
.GroupJoin(orders,
c => c.Id,
o => o.CustomerId,
(c, orderGroup) => new { Customer = c, Orders = orderGroup })
.SelectMany(
x => x.Orders.DefaultIfEmpty(),
(x, order) => new { x.Customer, Order = order });
输出说明:若客户没有订单,Order 字段为 null。可在 select 中进一步处理。
二、string.Join 性能优化 —— 什么时候用它,什么时候用 StringBuilder
string.Join 专为已知列表/数组的快速拼接设计,内部使用 FastAllocateString 和指针操作,性能极佳。但在动态循环中反复追加,则必须使用 StringBuilder。
场景对比:
| 场景 | 推荐方法 | 原因 |
|---|---|---|
string[] names = { "A", "B" } |
string.Join |
内部已预先算好总长度,一次分配内存,比循环 += 快 10~100 倍 |
foreach 动态添加未知数量的字符串 |
StringBuilder |
不会频繁创建新字符串对象,避免内存碎片 |
错误示例(性能极差):
string result = "";
foreach(var s in list) result = result + s + ","; // 每次+都新建字符串
正确做法:
var sb = new StringBuilder();
foreach(var s in list) sb.Append(s).Append(',');
string result = sb.ToString().TrimEnd(',');
三、Entity Framework Core 中的 Join 性能优化
EF Core 中滥用 Join 可能导致生成低效的 SQL(例如 N+1 子查询或不必要的笛卡尔积)。
问题一:默认 Join 生成 INNER JOIN 但会失去导航属性的预加载能力
优化:优先使用导航属性 + Include,而不是手动 Join。
// 推荐
var orders = context.Orders.Include(o => o.Customer).ToList();
// 不推荐(虽然功能相同,但无法利用 Include 缓存)
var query = context.Orders.Join(context.Customers, ...);
问题二:多表 Join 导致笛卡尔积膨胀
当关联三张以上表且均为一对多时,EF 默认会生成一个包含所有表的单条 SQL,结果集行数 = 表1行 × 表2匹配数 × 表3匹配数,数据量爆炸。
解决方案:使用 AsSplitQuery() 强制 EF 生成多条 SQL,在客户端内存中关联。
var query = context.Orders
.Join(context.Customers, ...)
.Join(context.Shippers, ...)
.AsSplitQuery() // 关键
.ToList();
注意:AsSplitQuery 会增加数据库往返次数,但能大幅减少数据传输量。适用于连接结果行数远大于原始表总行数的场景。
总结
- LEFT JOIN 的标准写法 =
GroupJoin+SelectMany+DefaultIfEmpty - 已知集合用
string.Join,动态循环用StringBuilder - EF Core 中能用导航属性就别手动
Join,多表关联时评估是否使用AsSplitQuery()
我是刚子,一个还在写 .NET 的程序员。咱们下回见!