在 C# 开发中,Linq(Language Integrated Query)提供了强大的数据查询能力,尤其是在处理集合间的关联操作时。本文将详细解析 C# Linq 中的左关联查询,并通过实际案例说明其用法。
左关联查询基础
左关联(Left Join)是 SQL 中的一种关联查询方式,它会返回左表中的所有记录,无论右表中是否有匹配记录。在 Linq 中实现左关联需要使用GroupJoin
和DefaultIfEmpty
方法组合,或者使用查询语法中的into
和DefaultIfEmpty
关键字。
基本语法结构
csharp
var query = from left in leftTablejoin right in rightTableon left.Key equals right.Key into tempGroupfrom temp in tempGroup.DefaultIfEmpty()select new {LeftProp = left.Property,RightProp = temp?.Property};
这里的关键点是:
- 使用
into
关键字将右表分组到临时集合 - 使用
DefaultIfEmpty()
处理可能为空的情况 - 在结果选择器中使用空条件运算符
?.
处理可能的 null 值
实际案例分析
让我们通过一个实际案例来理解左关联的应用场景。假设我们有一个企业资产管理系统,需要查询特定日期范围内有资产检查记录的企业,并标记哪些企业有记录。
以下是完整的示例代码:
csharp
using System;
using System.Collections.Generic;
using System.Linq;namespace LinqLeftJoinDemo
{// 企业信息类public class Enterprise{public string Code { get; set; }public string Name { get; set; }public string Industry { get; set; }}// 资产检查记录类public class AssetCheckRecord{public string EnterpriseID { get; set; }public DateTime StartDate { get; set; }public DateTime EndDate { get; set; }public string MonthEndType { get; set; }public decimal CheckScore { get; set; }}// 资产类型枚举public enum PigAssets{FixedAssets,CurrentAssets,IntangibleAssets}class Program{static void Main(string[] args){// 模拟企业数据var enterprises = new List<Enterprise>{new Enterprise { Code = "E001", Name = "ABC公司", Industry = "制造业" },new Enterprise { Code = "E002", Name = "XYZ公司", Industry = "零售业" },new Enterprise { Code = "E003", Name = "123公司", Industry = "服务业" },new Enterprise { Code = "E004", Name = "TEST公司", Industry = "科技业" }};// 模拟资产检查记录数据var checkRecords = new List<AssetCheckRecord>{new AssetCheckRecord{EnterpriseID = "E001",StartDate = new DateTime(2023, 10, 1),EndDate = new DateTime(2023, 10, 31),MonthEndType = PigAssets.FixedAssets.ToString(),CheckScore = 95.5m},new AssetCheckRecord{EnterpriseID = "E003",StartDate = new DateTime(2023, 10, 1),EndDate = new DateTime(2023, 10, 31),MonthEndType = PigAssets.FixedAssets.ToString(),CheckScore = 88.0m}};// 查询条件DateTime startDate = new DateTime(2023, 10, 1);DateTime endDate = new DateTime(2023, 10, 31);string assetType = PigAssets.FixedAssets.ToString();// 第一步:筛选符合条件的检查记录var filteredRecords = checkRecords.Where(r => r.StartDate.Date == startDate.Date&& r.EndDate.Date == endDate.Date&& r.MonthEndType.Equals(assetType)).ToList();// 第二步:执行左关联查询var result = from e in enterprisesjoin r in filteredRecordson e.Code equals r.EnterpriseID into enterpriseGroupfrom g in enterpriseGroup.DefaultIfEmpty()select new{EnterpriseID = e.Code,Name = e.Name,HasCheckRecord = g != null,CheckScore = g?.CheckScore ?? 0};// 输出结果Console.WriteLine("企业资产检查记录查询结果:");Console.WriteLine("企业代码\t企业名称\t是否有检查记录\t检查得分");foreach (var item in result){Console.WriteLine($"{item.EnterpriseID}\t{item.Name}\t{item.HasCheckRecord}\t\t{item.CheckScore}");}}}
}
代码解析
上述代码实现了一个典型的左关联查询场景:
数据准备:创建了两个实体类
Enterprise
和AssetCheckRecord
,并初始化了示例数据。条件筛选:首先筛选出特定日期范围内且资产类型匹配的检查记录:
csharp
var filteredRecords = checkRecords.Where(r => r.StartDate.Date == startDate.Date&& r.EndDate.Date == endDate.Date&& r.MonthEndType.Equals(assetType)).ToList();
左关联查询:使用查询语法实现左关联,确保返回所有企业信息,并标记是否有检查记录:
csharp
var result = from e in enterprisesjoin r in filteredRecordson e.Code equals r.EnterpriseID into enterpriseGroupfrom g in enterpriseGroup.DefaultIfEmpty()select new { ... };
结果处理:通过
DefaultIfEmpty()
方法处理可能的空值情况,并使用空合并运算符??
提供默认值。
执行结果分析
运行上述代码,输出结果如下:
plaintext
企业资产检查记录查询结果:
企业代码 企业名称 是否有检查记录 检查得分
E001 ABC公司 True 95.5
E002 XYZ公司 False 0
E003 123公司 True 88
E004 TEST公司 False 0
可以看到,即使某些企业没有对应的检查记录(如 E002 和 E004),它们仍然会出现在结果集中,并且HasCheckRecord
字段被标记为False
,检查得分为 0。
方法语法实现左关联
除了查询语法,Linq 还提供了方法语法来实现左关联,以下是等效的方法语法实现:
csharp
var result = enterprises.GroupJoin(filteredRecords,e => e.Code,r => r.EnterpriseID,(e, group) => new { Enterprise = e, Records = group }).SelectMany(temp => temp.Records.DefaultIfEmpty(),(temp, r) => new{EnterpriseID = temp.Enterprise.Code,Name = temp.Enterprise.Name,HasCheckRecord = r != null,CheckScore = r?.CheckScore ?? 0});
这种方法语法虽然更加函数式,但理解起来可能稍复杂一些。