LINQ(LINQ to Entities)

LINQ to Entities 是 LINQ 中最吸引人的部分。它让你可以使用标准的 C# 对象与数据库的结构和数据打交道。使用 LINQ to Entities 时,LINQ 查询在后台转换为 SQL 查询并在需要数据的时候执行,即开始枚举结果的时候执行。LINQ to Entities 还为你获取的所有数据提供变化追踪,也就是说,可以修改查询获得的对象,然后整批同时把更新提交到数据库。

LINQ to Entities 是 Entity Framework 的一部分并且取代 LINQ to SQL 作为在数据库上使用 LINQ 的标准机制。Entity Framework 是行业领先的对象-关系映射(ORM)系统。可以和多种数据库一起使用,并支持各种灵活、复杂的数据模型。

注:

微软把开发的重点从 LINQ to SQL 转移到了 LINQ to Entities,并且宣布 LINQ to SQL 不再提供更新,LINQ to SQL 现在仍被支持单不推荐。

LINQ to Entities 是一项令人印象深刻的技术,但对大多数开发人员而言只是一个小的进步。和 DataSet 一样,ASP.NET 开发人员使用 LINQ 的查询特新远多于它的批量更新特性。这是因为通常 Web 应用程序的更新是单次的而不是批量的。他们更愿意在页面回发时立刻执行更新,同时可以获得原始值和新(更新)值,这使得通过 ADO.NET 命令提交更新更加方便。

简而言之,LINQ to Entities 没有提供任何不能用 ADO.NET代码、自定义对象、LINQ to Objects 实现的特性,但是有时出于某些原因而需要考虑使用 LINQ to Entities:

  • 更少的代码。不必编写查询数据库的 ADO.NET 代码,可以通过一个工具生成需要的数据类。
  • 灵活的查询能力。不必拼凑 SQL 语句,而是使用 LINQ 查询模型。一致的查询模型可访问众多不同的数据源(从数据库到 XML)。
  • 变更追踪以及批量更新。可以对查询的数据进行多项修改并提交批量更新,这不需要编写任何 ADO.NET 代码。

生成数据模型

Entity Framework 依赖于一个数据模型来使用 LINQ to Entities 进行查询。表中的行被转换为 C# 对象的实例,表的列是这些对象的属性。数据库架构和数据模型对象的映射是 Entity Framework 的核心.

为了生成模型,右击 App_Code 目录,单击“添加新项”,“ADO.NET 实体数据模型”,设置创建的文件名称后(这里是 NorthwindModel.edmx),单击“确定”。

从一个已经存在的数据库生成模型,即微软的 Northwind 示例数据库。配置数据库连接,并可以选择表、视图、和存储过程。还可以选择使用复数还是单数形式的对象名(例如,Products 表的行被命名为 Product )、是否包含外键关系等。这里选择全部表并选中“所生成对象的单复数形式”。

Visual Studio 会为你选择的数据库元素创建模型图,它显示了已经创建的映射对象、对象拥有的字段以及对象之间的关系。

项目中新增了下面这两个文件:

  • NorthwindModel.edmx:这个XML文件定义数据库模型的架构。
  • NorthwindModel.Designer.cs:这个C#代码文件包含数据模型的映射对象。

数据模型类

我们将把大部分时间花在 NorthwindModel.Designer.cs 这个文件上。因为它包含了我们要用于 LINQ to Entities 查询的数据类型。(这个文件会被数据模型重新生成,因此不应该也不必要手工去修改这个文件,你的修改会丢失。

打开该文件,可以看到有两段代码区域:Contexts 和 Entities 。

1. 派生的对象上下文

NorthwindModel.Designer.cs 文件中定义的第一个类从 ObjectContext 派生,其名称是 NorthwindEntities 。这个类的构造函数连接到所生成模型的数据库,或者你也可以指定连接字符串连接到其他数据库(必须具有相同的架构,否则模型无法工作)。

下面是一个简单的示例:

protected void Page_Load(object sender, EventArgs e)
{
    NorthwindEntities db = new NorthwindEntities();
    GridView1.DataSource = db.Products;
    GridView1.DataBind();
}

2. 实体类

实体类用于把数据库表的记录映射到C#对象。如果选中了“确定所生成对象的单复数形式”选项,那么像 Products 这样的表创建的实体对象名称是 Product。

每个实体对象包含如下的内容:

  • 工厂方法:可以通过默认的构造函数或工厂方法创建实体对象新实例。工厂方法的参数是需要的字段,它是试图保存数据元素时防止架构错误的好办法。
  • 字段属性:实体对象为它们派生的数据库表的每个列包含一个字段属性。
  • 导航属性:如果数据模型中包含了外键关系,实体对象就会包含帮助访问关联数据的导航属性。

提示:

实体类被声明为分部类,因此可以创建扩展功能,在重新生成数据模型时它不会被覆盖。

示例:

protected void Page_Load(object sender, EventArgs e)
{
    NorthwindEntities db = new NorthwindEntities();
    var result = from p in db.Products
                 where p.Discontinued == false
                 select new
                 {
                     ID = p.ProductID,
                     Name = p.ProductName
                 };
    GridView1.DataSource = result;
    GridView1.DataBind();
}

  ID=5 的产品不符合条件被过滤掉了

实体关系

实体类包含导航属性。通过导航属性可以在数据模型间移动而不需要考虑外键关系。看下面的示例:

protected void Page_Load(object sender, EventArgs e)
{
    NorthwindEntities db = new NorthwindEntities();
    var result = from c in db.Customers
                 let o = from q in c.Orders
                         where q.Employee.LastName != "King"
                         select q
                 where c.City == "London" && o.Count() > 5
                 select new
                 {
                     Name = c.CompanyName,
                     Contact = c.ContactName,
                     OrderCount = o.Count()
                 };
    GridView1.DataSource = result;
    GridView1.DataBind();
}

这个查询使用 Orders 导航属性查询每个与 Customer 关联的所有 Orders 。我们使用 Order 实体类型的 Employee 导航属性检查下了订单的员工的姓并过滤掉了姓等于“King”的数据。

使用导航属性,不必为每个实体类创建单独的查询就可以在数据模型间导航。

1. 一对多关系

一对多关系的导航属性通过强类型的 EntityCollection 来处理。针对某个关系选择合适记录的问题你不需要关心,它已经由外键关系处理了。因此选择某个用户的订单时,仅仅得到了那些 CustomerID 值和 Customer的CustomerID 属性值相同的 Order 实例。

使用 SelectMany 扩展方法进行 LINQ to Entities 查询,可以直接把 EntityCollection 类作为查询结果,它会在结果集合里包含所有匹配的结果。

示例如下:

protected void Page_Load(object sender, EventArgs e)
{
    NorthwindEntities db = new NorthwindEntities();
    IEnumerable<Order> orders = db.Customers
        // 定位 LAZYK 客户
        .Where(c => c.CustomerID == "LAZYK") 
        // 以上一个客户结果集为准,导航客户的 Orders 属性获得所有 Order
        .SelectMany(c => c.Orders); 
    GridView1.DataSource = orders;
    GridView1.DataBind();
}

也可以改写成隐式的 LINQ 表达式得到相同的结果:

var result = from o in db.Orders
             where o.CustomerID == "LAZYK"
             select o;

2. 一对一关系

对于一对一关系,有两个导航属性。

  • TReference:它返回的结果是 EntityReference<T> ,其中,T 是关联关系引用的实体类型。例如,Order 实体类型有一个名为 EmployeeReference 的导航属性,它返回 EntityReference<Employee> 。
  • T这个属性更有用。T 是它引用的实体类型。例如,Order 实体类型有一个名为 Employee 的方便的导航属性。

查询存储过程

在解决方案资源管理器双击 NorthwindModel.edmx 文件,打开数据模型图,按右键选择“从数据库更新”可导入存储过程。打开“实体数据模型浏览器”(在“视图”->“其他”菜单里可以找到),展开 NorthwindModel.Store 节点,打开存储过程目录,就会看到导入模型里的存储过程列表。

选中存储过程,右键添加函数导入,导入界面还可以选择“创建新的复杂类型”,然后可以如下使用它:

protected void Page_Load(object sender, EventArgs e)
{
    NorthwindEntities db = new NorthwindEntities();
    IEnumerable<ESBC_Result> results
        = from c in db.ESBC(DateTime.Now.AddYears(-20), DateTime.Now)
          select c;
    GridView1.DataSource = results;
    GridView1.DataBind();
}

此时返回的类型的名称就是先前选择自定义复杂类型的名称。

LINQ to Entities 查询揭秘

先前演示的 LINQ to Entities 的用法几乎和 LINQ to Objects 的用法完全一样。确实是这样(至少表面上是这样)。LINQ 最棒的一件事就是它对各种数据源保持高度的一致性。如果你知道如何使用基本的 LINQ 查询,就可以用它来查询对象、数据库、XML 等。

缺点是这种相似性来自对很多复杂性的隐藏。如果不小心,就会给数据库产生很大的负载。你应该花时间好好检查为了服务你的 LINQ to Entities 查询,究竟生成了什么样的 SQL 查询。通过 Entity Framework 查看 SQL 查询并不容易,需要把 LINQ to Entities 查询的结果转换为 System.Data.Objects.ObjectQuery 的实例并调用 ToTraceString()方法才行。

示例:

protected void Page_Load(object sender, EventArgs e)
{
    NorthwindEntities db = new NorthwindEntities();
    var result = from c in db.Customers
                 let o = from q in c.Orders
                         where q.Employee.LastName != "King"
                         select q
                 where c.City == "London" && o.Count() > 5
                 select new
                 {
                     Name = c.CompanyName,
                     Contact = c.ContactName,
                     OrderCount = o.Count()
                 };
    Label1.Text = (result as System.Data.Objects.ObjectQuery).ToTraceString();
}

很多时候像这样打印 SQL 查询并不现实。如果使用的是非 Express 版本的 SQL Server,可以使用 SQL Server Profile 工具。如果是 Express 版本的,那我们推荐使用 Anjlab 开发的开源免费的 SQL Profile,它很不错。

1. 迟到的过滤

一个导致不必要的数据库查询的常见原因是过滤查询中的数据太晚了,这是一个查询示例:

NorthwindEntities db = new NorthwindEntities();
IEnumerable<NorthwindModel.Customer> custs
    = from c in db.Customers
      where c.Country == "UK"
      select c;
IEnumerable<NorthwindModel.Customer> results
    = from c in custs
      where c.City == "London"
      select c;
GridView1.DataSource = results;
GridView1.DataBind();

这里的问题是,第一个查询从数据库检索 Country=UK 的所有记录。第二个查询应用于第一个查询的结果,但是它使用的是 LINQ to Objects,也就是说我们丢弃了从数据库请求的绝大部分数据(第一个查询就显得非常的浪费资源了)。

这个示例只会产生一条 SQL 查询,类似于于下面:

SELECT * FROM Customers WHERE Country=‘UK‘

解决方案是把过滤器融合到同一个查询里。

2. 使用延迟和贪婪数据加载

为了让导航属性无缝地工作,LINQ to Entities 使用了一项称作延迟加载的技术,只在需要的时候才从数据库加载数据。通过导航属性从某个实体类型转移到另一个实体类型时,第二个实体类型的实例仅在需要的时候才加载。

protected void Page_Load(object sender, EventArgs e)
{
    NorthwindEntities db = new NorthwindEntities();
 
    IEnumerable<NorthwindModel.Customer> custs
        = from c in db.Customers
          where c.Country == "UK" && c.City == "London"
          select c;
 
    List<string> names = new List<string>();
    foreach (NorthwindModel.Customer c in custs)
    {
        if (c.Orders.Count > 2)
        {
            names.Add(c.CompanyName);
        }
    }
 
    GridView1.DataSource = names;
    GridView1.DataBind();
}

这个查询中,我们过滤出一组 Customers,然后对其结果进行迭代,并导航到相关的 Order 实例,最终,我们得到了位于英国伦敦且订单多余两笔的公司名称。

由于延迟加载,Orders 表的数据只在需要时加载,也就是说为了在循环中得到每个客户关联的订单,我们都生成了一条 SQL 查询。这产生了太多的查询。对于这个简单的示例,我们可以把所有这一切整合到一个 LINQ 查询里。

但其实我们要演示的是贪婪加载功能。它可以在查询中加载其他表的关联数据。示例如下:

protected void Page_Load(object sender, EventArgs e)
{
    NorthwindEntities db = new NorthwindEntities();
 
    IEnumerable<NorthwindModel.Customer> custs
        = from c in db.Customers.Include("Orders")
          where c.Country == "UK" && c.City == "London"
          select c;
 
    List<string> names = new List<string>();
    foreach (NorthwindModel.Customer c in custs)
    {
        if (c.Orders.Count > 2)
        {
            names.Add(c.CompanyName);
        }
    }
 
    GridView1.DataSource = names;
    GridView1.DataBind();
}

使用 Include()扩展方法包含关联的数据,它告诉 LINQ to Entities 引擎关联到我们查询的 Customer 的 Order 实例应该被加载,即便这个查询并没有直接关联到 Orders 表。最终,Entity Framework 捕获了结果,也就是说当我们迭代 Customer 实例并检查关联的 Orders 时,它们都已经被加载了,不需要再生成额外的数据库查询。

3. 使用显式加载

如果要完全控制加载的数据,可以使用显式加载。可以使用派生 ObejectContext 类禁用延迟加载,然后使用 EntityCollection.Load()方法按需加载数据,可以通过 IsLoaded 方法检查所需的数据是否已经加载。

protected void Page_Load(object sender, EventArgs e)
{
    NorthwindEntities db = new NorthwindEntities();
    db.ContextOptions.LazyLoadingEnabled = false;
 
    IEnumerable<NorthwindModel.Customer> custs
        = from c in db.Customers
          where c.Country == "UK"
          select c;
    
    foreach (NorthwindModel.Customer c in custs)
    {
        if (c.City == "London")
        {
            c.Orders.Load();
        }
    }
 
    List<Order> orders = new List<Order>();
    foreach (NorthwindModel.Customer c in custs)
    {
        if (c.Orders.IsLoaded)
        {
            orders.Add(c.Orders.First());
        }
    }
 
    GridView1.DataSource = orders;
    GridView1.DataBind();
}
  1. 禁用了延迟加载,即导航属性要引用的数据不会被自动加载。
  2. 第一次迭代使用 Load()显式加载那些符合条件的 Orders 数据,此时数据会从数据库加载到 Entity Framework 缓存里。
  3. 第二次迭代检查所有的 Customers 对象,用 IsLoaded 属性判断哪些 Customers 加载了 Orders 数据
  4. First()方法可以把第一条 Order 添加到集合中

这个示例并不怎么自然,但它足以让你看出项目中实际使用显式加载所需的知识。

4. 编译查询

LINQ to Entities 另一个隐藏的功能是能够创建已编译的查询。已编译的查询是一个强类型的 Func 委托,它有一个用于查询的参数。编译查询时,执行翻译到 SQL 语句的动作,以后每次调用已编译的查询时都会对其重用。

这并不像使用存储过程那样高效,因为数据库还是要创建查询计划来执行 SQL ,但它确实避免了 LINQ to Entities 重复解析 LINQ 查询。

示例:

using System.Data.Objects;
 
public partial class Chapter13_DerivedObjectContext : System.Web.UI.Page
{
    // 封装一个具有两个参数并返回 TResult 参数指定的类型值的方法。
    Func<NorthwindEntities, string, IQueryable<NorthwindModel.Customer>> MyCompiledQuery;
    NorthwindEntities db;
 
    protected void Page_Load(object sender, EventArgs e)
    {
        // CompiledQuery 表示一个缓存的 LINQ to Entities 查询
        // Compile() 创建一个表示已编译的 LINQ to Entities 查询的新委托
        MyCompiledQuery = CompiledQuery.Compile<NorthwindEntities, string,
            IQueryable<NorthwindModel.Customer>>((context, city) =>
                from c in context.Customers
                where c.City == city
                select c);
 
        db = new NorthwindEntities();
 
        GridView1.DataSource = MyCompiledQuery(db, "London");
        GridView1.DataBind();
    }
}
时间: 2024-08-12 01:56:45

LINQ(LINQ to Entities)的相关文章

LINQ(LINQ to DataSet)

http://www.cnblogs.com/SkySoot/archive/2012/08/21/2649471.html DataTable.Select()方法使用和 SQL 相似的过滤语法从 DataTable 中提取你关心的记录,虽然 Select()可以很好的工作,但它还是有一些明显的限制.首先,它是基于字符串的,也就是说可能的错误不能在编译的时候发现.其次,它的过滤功能也很有限,它没有提供 LINQ 操作符能够提供的其他特性,如排序.分组以及投影. 使用 LINQ to DataS

什么是LINQ? (上)

什么是LINQ?LINQ(Language Integrated Query)的全称就是语言集成查询,是为了各种不同的数据源提供一个统一的接口.通过这个接口,查询各种数据源可以使用将近一致的方式和语法.根据各种不同的数据源,接口有着不同的实现.而接口的调用者,通常是各种各样的应用程序.根据数据源的不同,产生了不同的LINQ分支,如LINQ To Objects, LINQ To SQL, LINQ To XML等.下面先简单说一下LINQ涉及到的基础知识. 一.泛型委托  委托是方法的抽象,泛型

EntityFramework 7 更名为EntityFramework Core(预发布状态)

前言 最近很少去学习和探索新的东西,尤其是之前一直比较关注的EF领域,本身不太懒,但是苦于环境比较影响自身的心情,所以迟迟没有下笔,但是不去学习感觉在精神层面缺少点什么,同时也有园友说EF又更新了,要我再写一篇,最终经过思想斗争后,还是花了一点时间去继续探索.本篇比较理论的去分享最近EF进展,后面有时间会继续关注EF团队在EF上的动向,并给出相对应的实例. EF Core 1.0.0 (1)EntityFramework是微软在.NET中推荐使用的数据访问技术,而EntityFramework

Lambda 表达式(C# 编程指南) 微软microsoft官方说明

Visual Studio 2013 其他版本 Lambda 表达式是一种可用于创建委托或表达式目录树类型的匿名函数. 通过使用 lambda 表达式,可以写入可作为参数传递或作为函数调用值返回的本地函数. Lambda 表达式对于编写 LINQ 查询表达式特别有用. 若要创建 Lambda 表达式,需要在 Lambda 运算符 => 左侧指定输入参数(如果有),然后在另一侧输入表达式或语句块. 例如,lambda 表达式 x => x * x 指定名为 x 的参数并返回 x 的平方值. 如下

Linq之旅:Linq入门详解(Linq to Objects)

示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集成查询).通过LINQ,我们可以使用相同API操作不同的数据源.接下来就让我们看看LINQ是什么以及如何使用? 再此之前,需要先了解的相关技术 1. 隐式类型.匿名类型.对象初始化器 1) 隐式类型,使用var关键字创建,C#编译器会根据用于初始化局部变量的初始值推断出变量的数据类型.(不过我个人认

LINQ(隐式表达式、lambda 表达式)

.NET 中一项突破性的创新是 LINQ(Language Integrated Query,语言集成查询),这组语言扩展让你能够不必离开舒适的 C# 语言执行查询. LINQ 定义了用于构建查询表达式的关键字.这些查询表达式能够对数据进行选择.过滤.排序.分组和转换.借助各种 LINQ 扩展,你可以对不同的数据源使用相同的查询表达式.        虽然你可以在任意地方使用 LINQ ,但是只有 ASP.NET 应用程序中最可能把 LINQ 用作数据库组件的一部分.你可以和 ADO.NET 数

Webform(Linq增删改查)

Linq高集成化的数据访问类,它会自动映射数据库结构,将表名完整映射成为类名,将列名完整映射成字段名数据库数据访问,能大大减少代码量.(1)Linq创建添加LINQ to SQL类,类名需与要连接的数据库名一样 Linq文件是dbml结尾,一个数据库对应一个Linq文件(2)数据库连接打开服务器资源管理器,点击下图黄圈内按钮,连接到数据库,弹出添加连接对话框,填写服务器名(.),选择使用什么身份验证,然后您要连接的数据库,然后将您要用的数据库拉到类中. (3)数据扩展 新建一个部分类(关键字:p

LINQ:开始使用 LINQ(五)- LINQ 中的查询语法和方法语法

开始使用 LINQ(五)- LINQ 中的查询语法和方法语法 在表示语言集成查询 (LINQ) 使用 LINQ 性查询语法,文档中的多数查询编写.但是,编译代码时,必须将查询语法转换为方法,这就需要 .NET 公共语言运行时 (CLR).这些方法调用标准查询运算符的名称类似 Where.Select.GroupBy.Join.Max和 Average.可以调用这些方法直接使用方法语法而不是查询语法. 查询语法和方法语法语义相同,但是,许多人员发现查询语法更简单.更易于阅读.某些查询必须表示为方法

LINQ 之四 (SkipWhile和TakeWhile)

看到书上的例子很正常,于是,我写了个不正常一点的. 书上的是这样的 class Program { static void Main(string[] args) { int[] mn = { 1,2,3,4,5,6,7,8,9}; var z = mn.TakeWhile((x, i) => i < 5); foreach(var x in z) { Console.WriteLine(x); } Console.ReadLine(); } } 结果是1 2 3 4 5 我把 i<5改