1 摘要
文章通过一个简单的实例对Linq中的Join操作进行演示,并在文章的最后对Join操作相关知识点进行简单的总结。
2 实例演示
1) 新建数据库MyTestDB,在数据库中新建数据表tb_Class和tb_Student,两表的定义如下图所示。
图1 tb_Class的定义 图2 tb_Student的定义
2) 向数据表tb_Class和tb_Student插入下图所示测试数据。
图3 tb_Class的测试数据
图4 tb_Student的测试数据
3) 新建控制台应用程序LinqJoinExp。
4) 本例中使用ADO.NET Entity Framework来访问数据库的数据,使用ADO.NET Entity Framework访问数据库前需要新建ADO.NET实体数据模型,本例中新建的ADO.NET实体数据模型名称为MyTestDB,具体操作可以参考文章《Ado.Net Entities Framework实例》。
5) 下面使用Join分别实现tb_Class左连接tb_Student,tb_Class右连接tb_Student,tb_Class内连接tb_Student操作。详细代码如下所示。
//************************************************************
//
// LINQ JOIN类示例代码
//
// Author:三五月儿
//
// Date:2014/08/02
//
// http://blog.csdn.net/yl2isoft
//
//************************************************************
using System;
using System.Linq;
namespace LinqJoinExp
{
class Program
{
static void Main(string[] args)
{
DoLeftJoin();//左连接
PrintStar();
DoRightJoin();//右连接
PrintStar();
DoJoin();//连接
}
/// <summary>
/// 左连接
/// </summary>
public static void DoLeftJoin()
{
using (MyTestDBEntities myTestDBEntities = new MyTestDBEntities())
{
var leftJoinResult = from c in myTestDBEntities.tb_Class
join s in myTestDBEntities.tb_Student
on c.classid equals s.classid into joinedResult
from jr in joinedResult.DefaultIfEmpty()
select new
{
ClassId = c.classid,
ClassName = c.classname,
Teacher = c.teacher != null ? c.teacher : string.Empty,
StudentId = jr.id != null ? jr.id : 0,
StudentName = jr.name != null ? jr.name : string.Empty,
StudentAge = jr.age != null ? jr.age : 0,
StudentScore = jr.score != null ? jr.score : 0,
};
foreach (var result in leftJoinResult)
{
Console.WriteLine("ClassId=" + result.ClassId + ","+
"ClassName=" + result.ClassName + ","+
"Teacher=" + result.Teacher + ","+
"StudentId=" + result.StudentId + ","+
"StudentName=" + result.StudentName + ","+
"StudentAge=" + result.StudentAge + ","+
"StudentScore=" + result.StudentScore);
}
}
}
/// <summary>
/// 右连接
/// </summary>
public static void DoRightJoin()
{
using (MyTestDBEntities myTestDBEntities = new MyTestDBEntities())
{
var rightJoinResult = from s in myTestDBEntities.tb_Student
join c in myTestDBEntities.tb_Class
on s.classid equals c.classid into joinedResult
from jr in joinedResult.DefaultIfEmpty()
select new
{
StudentId = s.id,
StudentName = s.name,
StudentAge = s.age,
StudentScore = s.score != null ? s.score : 0,
ClassId = jr.classid != null ? jr.classid : 0,
ClassName = jr.classname != null ? jr.classname : string.Empty,
Teacher = jr.teacher != null ? jr.teacher : string.Empty,
};
foreach (var result in rightJoinResult)
{
Console.WriteLine("ClassId=" + result.ClassId + "," +
"ClassName=" + result.ClassName + "," +
"Teacher=" + result.Teacher + "," +
"StudentId=" + result.StudentId + "," +
"StudentName=" + result.StudentName + "," +
"StudentAge=" + result.StudentAge + "," +
"StudentScore=" + result.StudentScore);
}
}
}
/// <summary>
/// 连接
/// </summary>
public static void DoJoin()
{
using (MyTestDBEntities myTestDBEntities = new MyTestDBEntities())
{
var joinResult = from c in myTestDBEntities.tb_Class
join s in myTestDBEntities.tb_Student
on c.classid equals s.classid
select new
{
ClassId = c.classid,
ClassName = c.classname,
Teacher = c.teacher != null ? c.teacher : string.Empty,
StudentId = s.id,
StudentName = s.name,
StudentAge = s.age,
StudentScore = s.score != null ? s.score : 0,
};
foreach (var result in joinResult)
{
Console.WriteLine("ClassId=" + result.ClassId + "," +
"ClassName=" + result.ClassName + "," +
"Teacher=" + result.Teacher + "," +
"StudentId=" + result.StudentId + "," +
"StudentName=" + result.StudentName + "," +
"StudentAge=" + result.StudentAge + "," +
"StudentScore=" + result.StudentScore);
}
}
}
/// <summary>
/// 显示型号分隔符
/// </summary>
public static void PrintStar()
{
Console.WriteLine("**************************************************************************");
}
}
}
6) 运行示例程序,得到下图所示结果。
图5 程序运行结果
7) 在阅读程序的运行结果前,我们先来对程序的结果进行一番预测。
首先,对连接的定义(借鉴SQL中的定义,可以参考文章《JOIN操作实例》)做简单的复习。
- 左外连接:从左表那里返回所有的行以及右表中符合指定的匹配条件的行。
- 右外连接:从右表那里返回所有的行以及左表中符合指定的匹配条件的行。
- 内连接:仅当至少有一个同属于两表的行符合连接条件时,内连接才返回行。
根据连接的定义,我们可以得到tb_Class左连接tb_Student,tb_Class右连接tb_Student,tb_Class内连接tb_Student的结果。
表1 tb_Class左连接tb_Student的结果
classid | classname | teacher | id | name | age | classid | score |
1 | 1班 | yul | 1 | zhangs | 12 | 1 | NULL |
1 | 1班 | yul | 4 | liulu | 12 | 1 | NULL |
2 | 2班 | wsp | 2 | lisi | 10 | 2 | 85 |
3 | 3班 | NULL | 3 | wangwu | 11 | 3 | 90 |
14 | 4班 | lisi | NULL | NULL | NULL | NULL | NULL |
表2 tb_Class右连接tb_Student的结果
classid | classname | teacher | id | name | age | classid | score |
1 | 1班 | yul | 1 | zhangs | 12 | 1 | NULL |
2 | 2班 | wsp | 2 | lisi | 10 | 2 | 85 |
3 | 3班 | NULL | 3 | wangwu | 11 | 3 | 90 |
1 | 1班 | yul | 4 | liulu | 12 | 1 | NULL |
NULL | NULL | NULL | 5 | liujia | 12 | 5 | 88 |
表3 tb_Class内连接tb_Student的结果
classid | classname | teacher | id | name | age | classid | score |
1 | 1班 | yul | 1 | zhangs | 12 | 1 | NULL |
1 | 1班 | yul | 4 | liulu | 12 | 1 | NULL |
2 | 2班 | wsp | 2 | lisi | 10 | 2 | 85 |
3 | 3班 | NULL | 3 | wangwu | 11 | 3 | 90 |
将我们根据定义计算得到的结果与程序的运行结果进行对比,两者完全一致。说明,本文的示例程序是正确的,所以,大家完全可以放心的参考本文的示例代码进行学习(文章最后会给出程序源码的下载地址)。这里,有一点需要补充说明一下,根据定义计算得到的结果中,存在大量的NULL值,但是示例程序结果中并不存在NULL值,这是因为示例程序对可能出现NULL值的字段都进行了处理,当某字段值为NULL时,就用0或者空串替换该字段的值,例如:Teacher = c.teacher != null ? c.teacher : string.Empty。
3 知识总结
1) 在Linq中,使用Join就可以完成左连接,右连接,内连接的操作。只是,在进行左右连接操作时,还需要借助于Enumerable.DefaultIfEmpty()方法,此方法位于命名空间System.Linq中,是一个扩展方法,该方法的作用是:返回指定序列的元素,如果序列为空时则使用序列元素对应类型的默认值代替,引用和可空类型的默认值为 null。
2) 通过本文所给示例,可以了解到,linq中左连接与右联接的代码实现没有任何区别,因为“tb_Class右连接tb_Student”本质上与“tb_Student左连接tb_Class”完全相同,所以在实现“tb_Class右连接tb_Student”时,本质上仍然使用的是左连接来实现的,因此,linq中的左右联接本质上都是左连接。
3) 最后,再次强调一下连接的定义,毕竟,只有充分理解了定义的基础上才能进行灵活地应用嘛,呵呵。
- 左外连接:从左表那里返回所有的行以及右表中符合指定的匹配条件的行。
- 右外连接:从右表那里返回所有的行以及左表中符合指定的匹配条件的行。
- 内连接:仅当至少有一个同属于两表的行符合连接条件时,内连接才返回行。