LINQ之路(2):LINQ to SQL本质

在前面一篇文章中回顾了LINQ基本语法规则,在本文将介绍LINQ to SQL的本质。LINQ to SQL是microsoft针对SQL Server的一个ORM的解决方案。既然是ORM,那对象(Object)、关系数据(Relation)和映射(Mapping)这三个组成要素是不可或缺的。本文将分为对象和关系数据的映射、从查询表达式到SQL的转换和DataContext数据上下文三个部分来说明。

1.对象和关系数据的映射

通过定义对象和关系数据的映射关系,我们可以以面向对象的方式来处理关系数据。通常,定义对象和关系数据的映射有两种方式:一是自定义特性,二是XML描述。前者是通过自定义一些描述关系数据结构的Attribute来修饰类,从而建立映射关系,而后者则是通过XML来描述这种映射关系。在LINQ to SQL中,为了简化工作,微软为我们提供了非常好用的工具SqlMetal,利用它,我们可以为 LINQ to SQL 的 .NET Framework 组件生成代码和映射。例如,我们可以利用

C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC>SqlMetal /conn:"server=.;
database=IT_Company_New;UID=sa;Password=123456" /language:csharp /namespace:jell
o.test /code:d:\CompanyDataContext.cs

来生成数据上下文代码,我们也可以利用

C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC>SqlMetal /conn:"server=.;
database=IT_Company_New;UID=sa;Password=123456" /dbml:d:\IT_Company_new.dbml

来生成中间数据库标记语言(.dbml)文件。

其实,这也是LINQ to SQL设计器采取的方式。

通过LINQ to SQL设计器将会为我们生成一下三个文件:

  1. .dbml文件:中间数据库标记语言文件,描述映射,由SqlMetal生成
  2. .dbml.layout文件:设计器界面布局文件,描述组件位置
  3. .designer.cs文件:生成代码部分,由SqlMetal生成

这里详细介绍下生成的代码,代码大致分为两个部分:数据上下文和表对象。数据上下文对应数据库,表对象。数据上下文继承自System.Data.Linq.DataContext,表对象继承自INotifyPropertyChanging, INotifyPropertyChanged。数据上下文采用AttributeMappingSource来指定映射方式,除了AttributeMappingSource方式还可以采用XmlMappingSource,使用DatabaseAttribute修饰以映射数据库,表对象使用TableAttribute修饰以映射表。数据上下文包含表对应的Table属性以及一些CURD可扩展性方法定义,表对象包含由表字段生成的属性(由ColumnAttribute和AssociationAttribute修饰以映射表字段)以及一些OnLoaded、OnValidate、OnCreated、On×××Changing和On×××Changed可扩展性方法定义。数据上下文可能还会包含存储过程及自定义函数的实现。数据上下文默认采用AttributeMappingSource来指定映射方式,相关的Attribute可参见MSDN

2.将查询表达式转换为SQL

var query = from student in _dataContext.T_Student
where student.Age < 20
select student;
foreach (var student in query)
{
Console.WriteLine(student.Name);
}

上面的查询表达式可以从数据库中检索出年龄小于20岁的学生。那么它是如何工作的呢?

这和我们上一篇中的写法几乎完全一样,唯一的区别是上一篇中数据源是IEnumerable或List类型的,它们继承自IEnumerable接口,而这里的数据源是Table类型的,它继承自IQueryable接口,而IQueryable继承自IQueryable,IQueryable又继承自IEnumable接口,这里真正起作用的是IQueryable接口,定义如下:

public interface IQueryable : IEnumerable
{
Type ElementType { get; }
Expression Expression { get; }
IQueryProvider Provider { get; }
}

包含三个属性:

ElementType:包含元素类型

Expression:要执行的操作的Expression

Provider:实现了IQueryProvider接口的Provider

再来看下IQueryProvider接口定义:

public interface IQueryProvider
{
IQueryable CreateQuery(Expression expression);
IQueryable<TElement> CreateQuery<TElement>(Expression expression);
object Execute(Expression expression);
TResult Execute<TResult>(Expression expression);
}

包含四个方法,CreateQuery和Execute分别一个泛型和一个非泛型方法。

  • CreateQuery:构造一个IQueryable或IQueryable对象,该对象可计算指定表达式目录树所所示的查询。

  • Execute:执行指定表达式目录树所表示的查询。

通过这种接口的设计,IQueryable允许我们创建支持其它非SQL Server数据库的实现。Provider将借助于IQueryable所提供的种种信息把查询语句转换为另一种方式,转换的实际操作将由CreateQuery方法实现,随后转换的结果将由Execute方法使用。

LINQ使用哪种方式进行转换,取决于数据源类型。若数据源未实现IQueryable或IQueryable接口而实现了IEnumerable或IEnumerable接口,则一般是通过是Enumerable的扩展方法转换成在内存中的操作的,而若实现了IQueryable或IQueryable接口,则一般是通过Queryable的扩展方法转换成表达式目录树。下面以一个简单的例子来说明它的转换流程:

var query = from student in _dataContext.T_Student
where student.Age < 22
let Len = student.Name.Length
orderby Len
select student;
foreach (var student in query)
{
Console.WriteLine(student.Name);
}

步骤如下:

1.查询表达式转换为扩展方法:

var query = _dataContext.T_Student.Where(student => student.Age < 22)
.Select(student => new { Len = student.Name.Length, student = student })
.OrderBy(student => student.Len)
.Select(student => new
{
ID = student.student.ID,
Name = student.student.Name,
Age = student.student.Age,
ClassID = student.student.ClassID
});

2.生成表达式目录树,通过调用Queryable一系列的扩展方法构造IQueryable对象。在扩展方法中通过source.Provider.CreateQuery方法来构造当前表达式目录树。如下图所示:

3.在进行迭代等真正获取数据操作中调用source.Provider.Execute方法来执行表达式以获取结果。这个过程比较复杂,在Execute方法中,.net framework是通过ExpressionVisitor类以Visitor模式方式来将表达式目录树解析,根据AttributeMapping或XmlMapping映射规则生成如下SQL,然后执行该SQL。

SELECT [t1].[ID], [t1].[Name], [t1].[Age], [t1].[ClassID]
FROM (
SELECT LEN([t0].[Name]) AS [value], [t0].[ID], [t0].[Name], [t0].[Age], [t0].[ClassID]
FROM [dbo].[T_Student] AS [t0]
) AS [t1]
WHERE [t1].[Age] < @p0
ORDER BY [t1].[value]

3.DataContext数据上下文

在LINQ to SQL中,DataContext是一个不能不说的东东。它拥有这么几个功能:管理业务实体、管理数据库连接、数据库映射、查询转换、对象标识和跟踪变化。在DataContext中维护着Table这些业务实体,可以很方便地这些业务实体进行操作。对于数据库连接的管理,DataContext是通过IProvider凭借IConnectionManager来进行管理的。LINQ to SQL默认使用的AttributeMapping来进行数据库映射的,通过DatabaseAttribute映射某一具体数据库,通过TableAttribute映射表,通过ColumnAttribute映射表字段。DataContext是通过借助于Table来实现将查询表达式转换为SQL的。DataContext通过CommonDataServices进行对象标识和变化跟踪的,在CommonDataServices对象中有IdentityManager和ChangeTracker两个属性,前者用于对象标识,后者用于变化跟踪。

每次查询数据库时,DataContext都会使用IdentityManager来判断是否已有某个对象的缓存,若已缓存则DataContext从内部缓存中获取。

在修改业务实体时,DataContext将会使用ChangeTracker同时保留修改前后的两个值,当提交修改过的记录时才会向数据库发出请求。

LINQ之路(2):LINQ to SQL本质的更多相关文章

  1. LINQ之路10:LINQ to SQL 和 Entity Framework(下)

    在本篇中,我们将接着上一篇“LINQ to SQL 和 Entity Framework(上)”的内容,继续使用LINQ to SQL和Entity Framework来实践“解释查询”,学习这些技术 ...

  2. LINQ之路 4:LINQ方法语法

    书写LINQ查询时又两种语法可供选择:方法语法(Fluent Syntax)和查询语法(Query Expression). LINQ方法语法是非常灵活和重要的,我们在这里将描述使用链接查询运算符的方 ...

  3. LINQ之路系列文章导读

    本系列文章将会分为3篇来进行阐述,如下: LINQ之路(1):LINQ基础 LINQ之路(2):LINQ to SQL本质 LINQ之路(3):LINQ扩展

  4. LINQ之路15:LINQ Operators之元素运算符、集合方法、量词方法

    本篇继续LINQ Operators的介绍,包括元素运算符/Element Operators.集合方法/Aggregation.量词/Quantifiers Methods.元素运算符从一个sequ ...

  5. LINQ之路 7:子查询、创建策略和数据转换

    在前面的系列中,我们已经讨论了LINQ简单查询的大部分特性,了解了LINQ的支持计术和语法形式.至此,我们应该可以创建出大部分相对简单的LINQ查询.在本篇中,除了对前面的知识做个简单的总结,还会介绍 ...

  6. LINQ之路(3):LINQ扩展

    本篇文章将从三个方面来进行LINQ扩展的阐述:扩展查询操作符.自定义查询操作符和简单模拟LINQ to SQL. 1.扩展查询操作符 在实际的使用过程中,Enumerable或Queryable中的扩 ...

  7. LINQ之路16:LINQ Operators之集合运算符、Zip操作符、转换方法、生成器方法

    本篇将是关于LINQ Operators的最后一篇,包括:集合运算符(Set Operators).Zip操作符.转换方法(Conversion Methods).生成器方法(Generation M ...

  8. [转]LINQ之路系列博客导航

    分享一个学习Linq的好博客:Linq之路

  9. LINQ之路(1):LINQ基础

    本文将从什么是LINQ(What).为什么使用LINQ(Why)以及如何使用LINQ(How)三个方面来进行说明. 1.什么是LINQ LINQ(Language Integrated Query)是 ...

随机推荐

  1. Maven + Jetty + Jersey搭建RESTful服务

    IntelliJ IDEA + Maven + Jetty + Jersey搭建RESTful服务 本文参考以下内容: 使用Jersey实现RESTful风格的webservice(一) Starti ...

  2. Android Bitmap 载入与像素操作

    Android Bitmap 载入与像素操作 一:载入与像素读写 在Android SDK中,图像的像素读写能够通过getPixel与setPixel两个Bitmap的API实现. Bitmap AP ...

  3. IOS开发应用

    IOS开发应用 我的第一个IOS开发应用 1. 需求描述 2. 开发环境介绍 3. 创建一个工程 4. 工程配置介绍 5. 目录结构介绍 6. 界面设置 7. 关联输入输出 8. 关联事件代码 9.  ...

  4. 在web浏览器中判断app是否安装并直接打开

    最近公司App产品在运营推广上有一个需求,就是要求可以让用户在访问我们的推广网页时,就可以判断出这个用户手机上是否安装了我们的App,如果安装了则可以直接在网页上打开,否则就引导用户前往下载.从而形成 ...

  5. java去全半角空格,trim(), replaceAll(" +",""),replaceAll("\\s*", ""), replaceAll(" | ", "")

    JAVA中去掉空格 . String.trim() trim()是去掉首尾空格 .str.replace(" ", ""); 去掉所有空格,包括首尾.中间 St ...

  6. registerWithTouchDispatcher()函数的使用

    registerWithTouchDispatcher()函数的使用 registerWithTouchDispatcher()函数主要用于注册Touch事件. 当我们使用this->setTo ...

  7. 详细分析Java中断机制(转)

    1. 引言 当我们点击某个杀毒软件的取消按钮来停止查杀病毒时,当我们在控制台敲入quit命令以结束某个后台服务时……都需要通过一个线程去取消另一个线程正在执行的任务.Java没有提供一种安全直接的方法 ...

  8. JAVA 数据权限设计

    数据权限设计 前言 在各种系统中.要保证数据对象的安全性以及易操作性,使企业的各业务部门.职能部门可以方便并且高效的协同工作,那么一个好的数据权限管理设计就成为一个关键的问题.尽管企业中各个单元的工作 ...

  9. 正确Linux新手很实用20命令

     //正确Linux新手很实用20命令 //slwang  2014.4.19 1, ls list directory contents 内容 ls -l     //以详情模式(long li ...

  10. Oracle SQL Lesson (7) - 使用子查询

    使用子查询简单子查询SELECT select_listFROM tableWHERE expr operator (SELECT select_list FROM table);子查询可以出现在se ...