首页 > 代码库 > Entity Framwork——Left Join

Entity Framwork——Left Join

在Entity Framework中使用left outer join 想必你们很多人都用过,但是为什么要那么用,我们一般也没去深究,这里稍微做一下探讨,不对的希望大家指正。

先准备我们的数据对象,这里使用最简单的one to many ,一个Category 对应多个Product,代码如下:

class Category{    public int CategoryId { get; set; }    public string CategoryName { get; set; }}class Product{    public int ProductId { get; set; }    public string ProductName { get; set; }    public int CategoryId { get; set; }}

查询代码:

 

var categories = new List<Category>(){    new Category() {CategoryId = 1, CategoryName = "Category-1"},    new Category() {CategoryId = 2, CategoryName = "Category-2"},    new Category() {CategoryId = 3, CategoryName = "Category-3"}};var products = new List<Product>(){    new Product() {ProductId = 1, CategoryId = 1, ProductName = "product c1-p1"},    new Product() {ProductId = 2, CategoryId = 1, ProductName = "product c1-p2"},    new Product() {ProductId = 3, CategoryId = 2, ProductName = "product c2-p3"},    new Product() {ProductId = 4, CategoryId = 2, ProductName = "product c2-p4"},    //new Product() {ProductId = 5, CategoryId = 3, ProductName = "product c3-p5"}};var result = from c in categories    join p in products on c.CategoryId equals p.CategoryId into g    from item in g.DefaultIfEmpty()    select c;

 

这个地方,我们一般有3个地方不明白,

1. g.DefaultIfEmplty()这个函数是什么意思?为什么要使用它?

2. into g,这个g到底是一个什么格式的数据?

3. 使用Into 和不使用 into 有什么区别?为什么要使用into?

接下来我通过一些代码,来解答以上三个问题:

1.  DefaultIfEmplty 这个函数是什么意思?为什么要使用它?

首先我们从字面上就可以看出这个函数大体是干啥的,如果为空就返回默认值,然后去看看微软官方是怎么解释这个函数的,

方法的签名:

public static IEnumerable<TSource> DefaultIfEmpty<TSource>(	this IEnumerable<TSource> source)
这个函数的解释:
Returns the elements of the specified sequence or the type parameter‘s default value in a singleton collection if the sequence is empty.
返回指定序列的元素;如果序列为空,则返回单一实例集合中的类型参数的默认值。
我相信很多人看到这个解释后,都一头雾水,本来有点明白的,结果一看反而不明白了,先看这个方法的第一个参数有一个前缀this, 就知道这是一个扩展方法了,
而且是扩展了所有实现了IEnumerabe<TSource>接口的类,说白了就是对序列元素的一个扩展(数组,list, 等我们经常使用的那些),看例子:
var categories = new List<Category>();var defaultCategories = categories.DefaultIfEmpty();Console.WriteLine("category‘s count: {0}",categories.Count);Console.WriteLine("defaultCategories‘s count: {0}",defaultCategories.Count());Console.WriteLine("loop defaultCategories--------------");foreach (var category in defaultCategories){    if (category == null)        Console.WriteLine("category is null");    else        Console.WriteLine(category.CategoryId);}/*    This code produces the following output:    category‘s count: 0    defaultCategories‘s count: 1    loop defaultCategories--------------    category is null    */
我们可以看到,categories有0个元素,但是defaultCategories有1个元素,然后循环defaultCategories发现,它有一个null的元素,看到这里这个方法的作用,
我相信都看的非常清楚了。
2. into g,这个g到底是一个什么格式的数据?废话不多说,看代码:
var result = (from c in categories    join p in products on c.CategoryId equals p.CategoryId into g    select g).FirstOrDefault();foreach (var ret in result){    Console.WriteLine(ret.ProductName);}/*    * This code produces the following output:    * product c1-p1    * product c1-p2    */

我们可以看到g 实际上是一个List<Product>的集合。

3. 使用Into 和不使用 into 有什么区别?为什么要使用into?

不使用 INTO 时查询出来的数据是一个平面二维的数据表,类似于这样的

Category-1product c1-p1
Category-1product c1-p2
Category-2product c2-p3
Category-2product c2-p3

用代码验证一下:

var result = from c in categories    join p in products on c.CategoryId equals p.CategoryId    select new {c.CategoryName, p.ProductName};foreach (var ret in result){    Console.WriteLine("{0}----{1}",ret.CategoryName,ret.ProductName);}/** This code produces the following output:* Category-1----product c1-p1* Category-1----product c1-p2* Category-2----product c2-p3* Category-2----product c2-p4*/
当使用INTO 是数据查询出来是一个类似字典的集合<Category, List<Product>>这样的一个集合,本来想弄个表格来展示的,折腾了半天怎么也不能合并行,只好作罢
改用文字描述吧:
Category-1, Products:{product 1, product2}; 
Category-2, Products: {product 3, product 4};
大体就是上面这个样子了,同样我们整段代码瞧瞧:
var result = from c in categories    join p in products on c.CategoryId equals p.CategoryId into g    select new {c, g};foreach (var ret in result){    Console.Write("{0}: ",ret.c.CategoryName);    foreach (var product in ret.g)    {        Console.Write("  {0}",product.ProductName);    }    Console.WriteLine();}/** This code produces the following output:* Category-1:   product c1-p1  product c1-p2* Category-2:   product c2-p3  product c2-p4*/
 
OK,三个问题,我们都明白了,现在开始说正事,先看看inner join, 也就是不用DefaultIfEmpty方法的时候
Inner Join
var result = from c in categories    join p in products on c.CategoryId equals p.CategoryId into g    from d in g    select new {CName= c.CategoryName, PName= d.ProductName};foreach (var ret in result){    Console.WriteLine("{0}---{1}",ret.CName,ret.PName);}/** This code produces the following output:* Category-1---product c1-p1* Category-1---product c1-p2* Category-2---product c2-p3* Category-2---product c2-p4*/
Left Join
var result = from c in categories    join p in products on c.CategoryId equals p.CategoryId into g    from d in g.DefaultIfEmpty()    select new {CName= c.CategoryName, PName= d==null? "no products" : d.ProductName};foreach (var ret in result){    Console.WriteLine("{0}---{1}",ret.CName,ret.PName);}/** This code produces the following output:* Category-1---product c1-p1* Category-1---product c1-p2* Category-2---product c2-p3* Category-2---product c2-p4* Category-3---no products*/
看到不同之处了没,有三个个地方不同,
第一个:使用inner join的时候,我们直接使用 [from d in g], 但是在left join时用的是[from d in g.DefaultIfEmpty()], 
第二个:我们在left join 的select 子句中,去判断了d是否等于null:[select new {CName= c.CategoryName, PName= d==null? "no products" : d.ProductName}; ] .
    为什么要这么做,就是应为如果当一个category没有找到匹配的product集合时,我们通过DefaultIfEmpty返回了一个含有一个null 元素的List<Product> 集合,
    然后我们[from d in g.DefaultIfEmpty()], 那么这个d就有可能为Null了,
    这就是我们为什么要去判断d 是否为空的原因了。
第三个:这个不同就是输出信息的不同了,最后多了个[Category-3---no products],从这里我们终于看到了有一个category 没有对应的产品,这就是left outer join

终于把Linq to object 的Left Join说明白了,但是还是跟我们的Entity Framework没一根毛的关系,我们创建一个Category, Product表,然后通过DB first来创建我们的entity 实体,

下面是建表语句:

CREATE TABLE Category(CategoryId INT PRIMARY KEY IDENTITY(1,1),CategoryName VARCHAR(255));CREATE TABLE Product(ProductId INT PRIMARY KEY IDENTITY(1,1),ProductName VARCHAR(255),CategoryId INT);ALTER TABLE ProductADD CONSTRAINT FK_Category_ProductFOREIGN KEY(CategoryId)REFERENCES Category(CategoryId);INSERT INTO CategoryVALUES(Category-1);INSERT INTO CategoryVALUES(Category-2);INSERT INTO CategoryVALUES(Category-3);INSERT INTO Product(ProductName, CategoryId)VALUES(Product c1-p1, 1);INSERT INTO Product(ProductName, CategoryId)VALUES(Product c1-p2, 1);INSERT INTO Product(ProductName, CategoryId)VALUES(Product c1-p3, 2);INSERT INTO Product(ProductName, CategoryId)VALUES(Product c1-p4, 2);GO

怎么通过db first 建立entity 实体,这里不讲,准备工作做好后,来看下面代码:

using (var ctx = new EFInActionEntities()){    var result = from c in ctx.MyCategories        join p in ctx.MyProducts on c.CategoryId equals p.CategoryId into g        from d in g.DefaultIfEmpty()        select new {CName = c.CategoryName, PName = d == null ? "No Products" : d.ProductName};    foreach (var ret in result)    {        Console.WriteLine("{0}--{1}", ret.CName,ret.PName);    }}/** Category-1--Product c1-p1* Category-1--Product c1-p2* Category-2--Product c1-p3* Category-2--Product c1-p4* Category-3--No Products*/

可以看到,输出的结果,和Linq to Object 时是一样的,看看生成的SQL吧:

SELECT [Extent1].[CategoryId]   AS [CategoryId],       [Extent1].[CategoryName] AS [CategoryName],       CASE         WHEN ([Extent2].[ProductId] IS NULL) THEN NNo Products         ELSE [Extent2].[ProductName]       END                      AS [C1]FROM   [dbo].[Category] AS [Extent1]       LEFT OUTER JOIN [dbo].[Product] AS [Extent2]         ON [Extent1].[CategoryId] = [Extent2].[CategoryId]

差不多就这些了,不知道你们明白了没有,关键点就是要搞明白 into 语句, 和into 语句得到的数据格式!