C#进阶-LINQ表达式之多表查询Ⅱ

简介: 本篇文章我们将演示LINQ扩展包基础语法里的多表查询 ,以Join连接查询为主要内容。目前LINQ支持两种语法,我会在每个案例前先用大家熟知的SQL语句表达,再在后面用C#的两种LINQ语法分别实现。LINQ语法第一次接触难免感到陌生,最好的学习方式就是在项目中多去使用,相信会有很多感悟。

多表查询Ⅱ

在学习之前,我们要做一些准备工作,我们需要创建User对象和包含User对象的集合,创建Salary对象和包含Salary对象的集合,作为后面查询和输出的数据源,参见这篇文章C#进阶之LINQ表达式总结完成准备工作。

数据源1:

数据源2:


1、Join连接查询分类

SQL中常见的连接查询有:

  • left join : 左连接,返回左表中所有的记录以及右表中连接字段相等的记录。
  • right join : 右连接,返回右表中所有的记录以及左表中连接字段相等的记录。
  • inner join : 内连接,又叫等值连接,只返回两个表中连接字段相等的行。
  • full join : 外连接,返回两个表中的行:left join + right join。
  • cross join : 结果是笛卡尔积,就是第一个表的行数乘以第二个表的行数。

Linq只有Join这个函数。

Linq中的Join连接查询是通过调换关联表和被关联表的顺序来转换左右连接的方向,通过调整Where和On等条件筛选函数的位置,来改变逻辑,实现更复杂的内连接全连接等功能。

同样,Linq的join方法也有两种写法:

/* C#写法1 <Linq写法>*/IEnumerable<Salary>SalaryList=fromuinlistjoinsinsalaryListonu.idequalss.user_idselects;
/*C#写法2 <Lambda写法>*/IEnumerable<Salary>SalaryList=list  .Join(
inner: salaryList, /*inner: 可以省略*/outerKeySelector: u=>u.id, /*outerKeySelector: 可以省略*/innerKeySelector: s=>s.user_id, /*innerKeySelector: 可以省略*/resultSelector: (u, s) =>s/*resultSelector: 可以省略*/  );

2、单条件Join连接查询

如上面两张表,数据表2的user_id是数据表1的外键,对应数据表1的id,可以通过关联查询把两张表不同的属性通过用户一一对应。

我们举个例子,利用关联查询查询表1用户信息和对应在表2的薪资信息:

/* SQL里的表达: 查询所有用户的姓名、年龄、职业、性别、是否在职和薪资*/SELECTu.id, u.name, u.age, u.occupation, u.gender, s.active, s.salaryFROMUserASuLEFTJOINSalaryASsONu.id=s.user_id;
/* C#写法1*/IEnumerable<UserSalary>UserSalaryList=fromuinlistjoinsinsalaryListonu.idequalss.user_idselectnewUserSalary  {
id=u.id,
name=u.name,
age=u.age,
occupation=u.occupation,
gender=u.gender,
active=s.active,
salary=s.salary  };
/*C#写法2*/IEnumerable<UserSalary>UserSalaryList=list  .Join(salaryList, u=>u.id, s=>s.user_id, (u, s) =>newUserSalary  {
id=u.id,
name=u.name,
age=u.age,
occupation=u.occupation,
gender=u.gender,
active=s.active,
salary=s.salary  });
/* 遍历 输出 */foreach (UserSalaryuserinUserSalaryList)
{
Console.WriteLine(PrintUserSalaryObject(user));
}
/* 输出结果 */{id=1, name=ZhangLong, age=38, gender=True, occupation=Teacher, active=True, salary=7800}
{id=2, name=ZhangJin, age=18, gender=False, occupation=Student, active=True, salary=1500}
{id=3, name=ZhangShuai, age=38, gender=False, occupation=Teacher, active=False, salary=8800}
{id=4, name=LiuGuangzhi, age=38, gender=False, occupation=Doctor, active=True, salary=12800}
{id=5, name=LiuZiming, age=38, gender=True, occupation=Doctor, active=True, salary=13600}
{id=6, name=LiuShuai, age=29, gender=False, occupation=Doctor, active=False, salary=29000}
{id=7, name=LiuJin, age=21, gender=True, occupation=Builder, active=True, salary=7000}
{id=8, name=JiangLong, age=38, gender=True, occupation=Builder, active=False, salary=8500}
{id=9, name=HuZiming, age=21, gender=True, occupation=Student, active=True, salary=2100}
{id=10, name=HuJin, age=21, gender=False, occupation=Student, active=True, salary=1300}



3、多条件Join连接查询

/* SQL里的表达: 用name和id两个属性关联用户表和薪资表,查询所有用户中性别是男性且在职的工资信息*//* SQL写法1*/SELECT*FROMUserASuRIGHTJOINSalaryASsONu.id=s.user_idANDu.name=s.nameANDu.gender=trueANDs.active=true;
/* SQL写法2*//*把筛选条件放到JoinOn后面的Where里可以避免左右连接带来的半壁空值的困扰,相当于先关联再筛选*/SELECT*FROMUserASuRIGHTJOINSalaryASsONu.id=s.user_idANDu.name=s.nameWHEREu.gender=trueANDs.active=true;
/* C#写法1*//*这种写法不推荐,结果集会有null,推荐关联后取得数据再做筛选,即把筛选条件写在Join后的结果集处理里*/IEnumerable<Salary>JointList= ( 
fromr1inlistwherer1.genderjoinr2in (fromr3insalaryListwherer3.activeselectr3) 
onnew  {
ID=r1.id,
r1.name  }
equalsnew  {
ID=r2.user_id,
r2.name  }
intoclsfromcincls.DefaultIfEmpty()
selectc).ToList();
/*C#写法2*/IEnumerable<Salary>JointList= ( 
fromr1inlistwherer1.genderjoinr2insalaryListonnew  {
ID=r1.id,
r1.name  }
equalsnew  {
ID=r2.user_id,
r2.name  }
intoclsfromcincls.DefaultIfEmpty()
wherec.activeselectc).ToList();
/*C#写法3*/IEnumerable<Salary>JointList= (
fromr1inlistfromr2insalaryListwherer2.active&&r1.id==r2.user_id&&r1.name==r2.name&&r1.genderselectr2).ToList();
/*C#写法4 <Lambda写法>*/IEnumerable<Salary>JointList=list.Where(u=>u.gender)
  .Join(
salaryList.Where(s=>s.active),
u=>new { ID=u.id, u.name },
s=>new { ID=s.user_id, s.name },
    (u, s) =>s  );
/*C#写法5 <Lambda写法>*//*把方法4对Salary集合的筛选放在整个连接查询的后面,因为gender是User的专有属性,所以gender的筛选不能放到内容对象为Salary的结果集后面*/IEnumerable<Salary>JointList=list.Where(u=>u.gender)
  .Join(
salaryList,
u=>new { ID=u.id, u.name },
s=>new { ID=s.user_id, s.name },
    (u, s) =>s  ).Where(s=>s.active);
/* 遍历 输出 */foreach (SalarysalaryinJointList)
{
if(salary!=null)
Console.WriteLine(PrintUserSalaryObject(salary));
}
/* 输出结果 */{id=1, name=ZhangLong, occupation=Teacher, active=True, salary=7800}
{id=5, name=LiuZiming, occupation=Doctor, active=True, salary=13600}
{id=7, name=LiuJin, occupation=Builder, active=True, salary=7000}
{id=9, name=HuZiming, occupation=Student, active=True, salary=2100}

4、自定义条件Join连接查询

/*Linq自定义条件的Join语法*/publicstaticSystem.Collections.Generic.IEnumerable<TResult>Join<TOuter,TInner,TKey,TResult> (
thisSystem.Collections.Generic.IEnumerable<TOuter>outer, 
System.Collections.Generic.IEnumerable<TInner>inner, 
Func<TOuter,TKey>outerKeySelector, //结果Tkey需要包含下列比较器需要的参数Func<TInner,TKey>innerKeySelector, //结果Tkey需要包含下列比较器需要的参数Func<TOuter,TInner,TResult>resultSelector, 
System.Collections.Generic.IEqualityComparer<TKey>comparer);
/*尤其注意这里相等比较器的传入参数是TKey类型,需和outerKeySelector和innerKeySelector的TKey类型保持一致*/

举个例子:

根据相同的姓名和职业关联User和Salary信息,查询满足雄性且在职的人的Salary信息。

/*新建一个相等比较器CompareUser*//*当两个User对象的name和occupation属性相等时返回true,否则为false*/classCompareUser : IEqualityComparer<User>{
publicboolEquals(Userx, Usery)
    {
if (x.name==y.name&&x.occupation.ToLower() ==y.occupation.ToLower())
returntrue;
returnfalse;
    }
publicintGetHashCode(Userobj)
    {
return (obj.name+obj.occupation).Length;
    }
}
/*Linq表达式实现找到符合CompareUser相等比较器的判等要求并且满足若干条件*/IEnumerable<Salary>JointList=list.Where(u=>u.gender)
  .Join<User, Salary, User, Salary>(  
inner: salaryList,
/*因为比较器用到了name和occupation两个属性,所以这里的Selector要包含这两个有用的属性值*/outerKeySelector: u=>newUser{ name=u.name, occupation=u.occupation},
innerKeySelector: s=>newUser{ name=s.name, occupation=s.occupation },
resultSelector: (u, s) =>s,
comparer: newCompareUser()
).Where(s=>s.active);
/* 遍历 输出 */foreach (SalarysalaryinJointList)
{
if(salary!=null)
Console.WriteLine(PrintUserSalaryObject(salary));
}
/* 输出结果 */{id=1, name=ZhangLong, occupation=Teacher, active=True, salary=7800}
{id=5, name=LiuZiming, occupation=Doctor, active=True, salary=13600}
{id=7, name=LiuJin, occupation=Builder, active=True, salary=7000}
{id=9, name=HuZiming, occupation=Student, active=True, salary=2100}

5、GroupJoin连接查询带分组

GroupJoin和Join的区别在于结果集进行了GroupBy分组,这里直接举一个多条件、自定义的最难例子,因为就是Join语法和GroupBy语法的结合体,不难理解,不多叙述。

/* GroupJoin语法 */IEnumerable<ListMultiGroupResult2>JointList=list.Where(u=>u.gender)
  .GroupJoin(
inner: salaryList.Where(s=>s.active),
outerKeySelector: u=>newUser { name=u.name, occupation=u.occupation },
innerKeySelector: s=>newUser { name=s.name, occupation=s.occupation },
resultSelector: (u, s) =>newListMultiGroupResult2 { 
Occupation=u.occupation, 
Name=u.name, 
SalaryList=s.ToList() },
comparer: newCompareUser()
  );
/*自定义相等比较器CompareUser*/classCompareUser : IEqualityComparer<User>{
publicboolEquals(Userx, Usery)
    {
if (x.name==y.name&&x.occupation.ToLower() ==y.occupation.ToLower())
returntrue;
returnfalse;
    }
publicintGetHashCode(Userobj)
    {
return (obj.name+obj.occupation).Length;
    }
}
/* 遍历 输出 */foreach (ListMultiGroupResult2sinJointList)
{
Console.WriteLine(s.Occupation+"/"+s.Name);
foreach (Salarysalaryins.SalaryList)
  {
Console.WriteLine(PrintSalaryObject(salary));
  }
}
/* 输出结果 */Teacher/ZhangLong{id=1, name=ZhangLong, occupation=Teacher, active=True, salary=7800}
Doctor/LiuZiming{id=5, name=LiuZiming, occupation=Doctor, active=True, salary=13600}
Builder/LiuJin{id=7, name=LiuJin, occupation=Builder, active=True, salary=7000}
Builder/JiangLongStudent/HuZiming{id=9, name=HuZiming, occupation=Student, active=True, salary=2100}
目录
相关文章
|
4月前
|
开发框架 .NET 编译器
C# 10.0中Lambda表达式的改进:更简洁、更灵活的代码编写体验
【1月更文挑战第21天】随着C#语言的不断发展,Lambda表达式作为一种简洁、高效的函数式编程工具,在C# 10.0中迎来了重要的改进。本文将详细探讨C# 10.0中Lambda表达式的新特性,包括参数类型的推断增强、自然类型的Lambda参数以及Lambda表达式的属性改进等。这些改进不仅简化了Lambda表达式的编写过程,还提升了代码的可读性和灵活性,为开发者带来了更优质的编程体验。
|
4月前
|
SQL 开发框架 .NET
C#进阶-LINQ实现对集合的增删改查
本篇演示了LINQ在日常开发中的常用操作,实现结果集的增删改查。目前LINQ支持两种语法,我会在每个案例前先用大家熟知的SQL语句表达,再在后面用C#的两种LINQ语法分别实现。LINQ语法第一次接触难免感到陌生,最好的学习方式就是在项目中多去使用,相信会有很多感悟。
38 0
|
2月前
|
开发框架 .NET C#
C#学习相关系列之Linq用法---where和select用法(二)
C#学习相关系列之Linq用法---where和select用法(二)
|
2月前
|
开发框架 .NET C#
C#学习相关系列之Linq用法---group和join相关用法(三)
C#学习相关系列之Linq用法---group和join相关用法(三)
|
2月前
|
开发框架 .NET C#
C#学习相关系列之Linq常用方法---排序(一)
C#学习相关系列之Linq常用方法---排序(一)
|
2月前
|
开发框架 .NET 编译器
C#学习相关系列之匿名方法和Lambda表达式
C#学习相关系列之匿名方法和Lambda表达式
|
2月前
|
开发框架 安全 .NET
C# .NET面试系列三:集合、异常、泛型、LINQ、委托、EF!
<h2>集合、异常、泛型、LINQ、委托、EF! #### 1. IList 接口与 List 的区别是什么? IList 接口和 List 类是C#中集合的两个相关但不同的概念。下面是它们的主要区别: <b>IList 接口</b> IList 接口是C#中定义的一个泛型接口,位于 System.Collections 命名空间。它派生自 ICollection 接口,定义了一个可以通过索引访问的有序集合。 ```c# IList 接口包含一系列索引化的属性和方法,允许按索引访问、插入、移除元素等。 由于是接口,它只定义了成员的契约,而不提供具体的实现。类似于 IEnumera
159 2
|
3月前
|
SQL 开发框架 .NET
C# Linq SaveChanges()报错 You have an error in your SQL syntex
C# Linq SaveChanges()报错 You have an error in your SQL syntex
10 0
|
4月前
|
开发框架 .NET 编译器
C# 9.0中的目标类型新表达式:类型推断的又一进步
【1月更文挑战第16天】C# 9.0引入了目标类型新表达式,这是类型推断功能的一个重要扩展。通过目标类型新表达式,开发者在创建对象时可以省略类型名称,编译器会根据上下文自动推断所需类型。这一特性不仅简化了代码编写,还提高了代码的可读性和维护性。本文将详细介绍目标类型新表达式的语法、使用场景及其对C#编程的影响。
|
4月前
|
机器学习/深度学习 开发框架 .NET
C# 中的 Lambda 表达式:一种简洁而强大的编程工具
【1月更文挑战第6天】在现代编程中,Lambda 表达式已经成为一种非常流行的编程范式。C# 作为一种功能强大的、面向对象的编程语言,自然也不例外。Lambda 表达式在 C# 中提供了一种简洁、灵活的方式来表示匿名函数。这些函数可以被用作委托或表达式树类型,从而在各种不同的上下文中使用。