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-1 product c1-p1
Category-1 product c1-p2
Category-2 product c2-p3
Category-2 product 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 Product
ADD CONSTRAINT FK_Category_Product
FOREIGN KEY(CategoryId)
REFERENCES Category(CategoryId);

INSERT INTO Category
VALUES(‘Category-1‘);

INSERT INTO Category
VALUES(‘Category-2‘);

INSERT INTO Category
VALUES(‘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 N‘No 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 语句得到的数据格式!

Entity Framwork——Left Join

时间: 2024-10-24 22:08:59

Entity Framwork——Left Join的相关文章

1.使用Entity Framwork框架常用的技术手段Code First 和Reverse Engineer Code First

提示:VS版本2013,  Entity Framwork版本5.0.0,Mysql数据库  使用Entity FrameWork的好处就不多说,直接上手如何使用.两种形式:1.将代码映射到数据库实体,使用的是Code First技术.2.将数据库实体转成代码,使用的是Reverse Engineer Code Fist技术. 一.Code First技术 1.建好项目,并加入一个名为CodeFirstDemo控制台应用程序项目. 2.配置.工具>>库程序包管理>>管理解决方案的N

记录一次BUG修复-Entity Framwork SaveChanges()失效

目录 一. 前言 二.问题背景 三.问题描述 四.问题解决步骤 六.总结 一. 前言 这是笔者在参与一个小型项目开发时所遇到的一个BUG,因为项目经验不足对Entity Framwork框架认识不足导致了这一BUG浪费了一天的时间,特此在这里记录.给自己一个警醒希望大家遇到相同问题能帮助到大家. 注:笔者水平有限,大家发现错误望批评指正. 二.问题背景 1.本次项目是一个ASP.NET MVC项目,因为项目比较小的关系,我们采用的是基本三层和仓储模式进行开发.2.使用的ORM框架是Entity

Entity Framwork系列之Model First

第一步 新建数据库 新建数据库TestDB2 第二步 ADO.NET实体数据模型 新建空的ADO.NET实体数据模型 新增实体(表)和类型(字段) 右键TestModel.edmx界面点击"根据模型生成数据库",执行生成的TestModel.edmx.sql文件中的SQL语句. 第三步 增删改查 using System; using System.Data; using System.Linq; using System.Windows.Forms; namespace EFDEMO

Entity Framwork 6 编译出错的问题(VS2012)

在VS2012中使用EF6上来建立一个Entity Data Model后啥都不干,编译都会报错. 错误如下: Value of type 'System.Data.Objects.ObjectParameter' cannot be converted to 'System.Data.Entity.Core.Objects.ObjectParameter'. Value of type 'System.Data.Entity.Core.Objects.ObjectResult(Of DataL

Entity FramWork - 在VS里面直接创建表,并同步到数据库

前面具体添加什么直接看: 1.Entity - 使用EF框架进行增删改查 - 模型先行 2.Entity - 使用EF框架进行增删改查 - 数据库先行 然后: 然后右键,可以添加[实体],也就是表.之后选择[从数据库更新模型].然后数据库中就添加了多张表.

Entity Framwork access database

原文链接 Pre-Requisites You will need to have Visual Studio 2010 or Visual Studio 2012 installed to complete this walkthrough. If you are using Visual Studio 2010, you will also need to have NuGet installed. 1. Create the Application To keep things simpl

EF(Entity Framwork)结构

初次接触EF,看了一些资料,将自己对EF结构的理解记录如下: EF的核心是EDM----实体数据模型(.edmx).它由三部分组成:概念模型(.csdl文件).存储模型(.ssdl文件).映射规范(.msl文件)组成. 概念模型:在EF中就是指实体类.  public  class  类名{ 属性1:    属性2: ...} 存储模型:在EF中是指数据库中的实体(将各个实体的关系固定到表中的形式). 映射:将概念模型和存储模型连接起来,以便进行操作.         即 :(概念模型) 实体.

Entity Framwork(EF) 7——在Controller内获取指定字段的值

一.开发背景: 在用户登录的时候,验证用户和密码是否正确.验证通过后将用户名和用户ID保存下来以便后续数据更新时使用. 二.用户验证方法: 1.创建DBContext 对象. ApplicationDbContext _context; //Controller构造函数        public FramController(ApplicationDbContext context)        {            _context = context;        } 2.查询数据

Entity Framwork(EF) 7——在现在数据库的甚而上开发MVC 新项目

一.开发背景: 由于老系统已经无法满足实际业务需求,需在现有数据库的甚而上开发新的项目. 二.困难点: 而EF默认情况下是要删除现有数据库表格后重新创建,这是不允许的.当你创建数据库对象时系统会提示“数据库中已存在名为 'XXXXX' 的对象” 三.解决方法: 1.创建测试数据库TEST. 2.创建数据库对象(在VS项目内). 3.将对象添加至DBContext(ApplicationDbContext). public DbSet<数据库对象类> 对象名 {get;set;} 4.在项目控制