C#中Join两个集合:如何获取双集合全量数据而非仅匹配项
嘿,我懂你现在的困惑——LINQ里默认的Join方法其实是内连接,只会返回两个集合里能匹配上的元素,这就是为啥你拿不到两边的全部数据。要实现类似SQL里的「全外连接」(FULL OUTER JOIN),得换个思路来写,我给你两种实用的解决方案,都是用原生LINQ就能搞定的:
先假设你的数据结构(方便示例)
先定义两个简单的实体类,模拟你要合并的集合:
// 用户集合的实体 public class User { public int Id { get; set; } public string Name { get; set; } } // 订单集合的实体 public class Order { public int UserId { get; set; } public string OrderNumber { get; set; } }
解决方案1:用GroupJoin + SelectMany 实现全外连接
这种方法通过左连接+右连接再合并的方式,覆盖所有数据:
// 初始化测试数据 var users = new List<User> { new User { Id = 1, Name = "Alice" }, new User { Id = 2, Name = "Bob" }, new User { Id = 3, Name = "Charlie" } }; var orders = new List<Order> { new Order { UserId = 1, OrderNumber = "ORD001" }, new Order { UserId = 2, OrderNumber = "ORD002" }, new Order { UserId = 4, OrderNumber = "ORD003" } }; // 第一步:左连接——拿到所有用户,以及他们对应的订单(没有订单的用户显示null) var leftJoin = users.GroupJoin( orders, user => user.Id, // 用户集合的匹配键 order => order.UserId, // 订单集合的匹配键 (user, userOrders) => new { User = user, Orders = userOrders.DefaultIfEmpty() } ).SelectMany( x => x.Orders, (x, order) => new { User = x.User, Order = order } ); // 第二步:右连接——拿到所有订单,以及它们对应的用户(没有用户的订单显示null) var rightJoin = orders.GroupJoin( users, order => order.UserId, user => user.Id, (order, orderUsers) => new { Order = order, Users = orderUsers.DefaultIfEmpty() } ).SelectMany( x => x.Users, (x, user) => new { User = user, Order = x.Order } ); // 第三步:合并左右连接结果,去重得到全外连接 var fullOuterJoin = leftJoin.Union(rightJoin);
代码说明:
GroupJoin会把左边集合的元素和右边匹配的元素分组,DefaultIfEmpty()确保没有匹配项时也会返回null,不会漏掉元素。SelectMany把分组后的结果展开成扁平的集合。Union自动去重,避免重复的匹配项同时出现在左连接和右连接中。
解决方案2:拆分合并(内连接 + 两边未匹配项)
这种方法更直观,分开处理「匹配项」「左边未匹配项」「右边未匹配项」,再合并:
// 第一步:拿到两个集合的匹配项(就是默认Join的结果) var matchedItems = users.Join( orders, u => u.Id, o => o.UserId, (u, o) => new { User = u, Order = o } ); // 第二步:拿到没有对应订单的用户 var usersWithoutOrders = users .Where(u => !orders.Any(o => o.UserId == u.Id)) .Select(u => new { User = u, Order = (Order)null }); // 第三步:拿到没有对应用户的订单 var ordersWithoutUsers = orders .Where(o => !users.Any(u => u.Id == o.UserId)) .Select(o => new { User = (User)null, Order = o }); // 第四步:合并所有结果 var fullOuterJoin = matchedItems.Concat(usersWithoutOrders).Concat(ordersWithoutUsers);
代码说明:
- 先通过
Join拿到所有匹配的元素,再分别筛选出两边没有匹配的元素,最后用Concat把三个部分合并起来。 - 这种写法逻辑清晰,适合刚接触LINQ的开发者理解。
小提示
- 如果你的集合里有重复的匹配键,记得根据业务需求处理去重逻辑。
- 如果是值类型(比如
int),要注意null的处理(可以用可空类型int?)。
内容的提问来源于stack exchange,提问作者Wojciech Pierscionek
相关产品推荐
相关产品推荐

