术语表
Built-in:内置的
Clause:子句
Debugger:调试器
Object Relational Mapper:对象关系映射器
ORM(Object Relation Mapping):对象关系映射
Visualizer:查看器
plug-in:插件程序
Breakpoint:断点
Shape:构造
object initialization:对象初始化
deferred execution model:延迟执行模型
sequences:序列
Object Initializer:对象初始化器
Collection Initializers:集合初始化器
上个月,我开始发表一个介绍LINQ TO SQL的随笔系列。LINQ TO SQL 是一个内置于.Net框架3.5版本的 O/RM (对象关系映射)框架,它使你可以方便地使用.Net类对关系数据库进行建模。你可以使用 LINQ 表达式来对数据库进行查询、添加、编辑、删除。
下面是我这系列随笔的前两篇:
l Part 1:LINQ TO SQL 介绍
l Part 2:定义我们的数据模型类
在今天这篇随笔中,我将继续详细为大家介绍如何使用我们在第二篇随笔中创建的这个数据模型,演示如何使用Asp.Net项目来对数据进行查询。
使用LINQ TO SQL建模了的 Northwind 数据库
在这一系列随笔中的第二篇,我一步步讲解了如何使用内置于VS2008中的LINT TO SQL设计器创建一个LINQ TO SQL类模型。下面是我们为Northwind范例数据库创建的类模型。
获取 Products
一旦我们定义了上面的数据模型类,我们可以方便的从我们的数据库中进行查询和获取数据。LINQ TO SQL通过对使用LINQ TO SQL设计器创建的NorthwindDataContext类编写LINQ查询语句来完成。
举个例子,如果想要获取一系列的Products对象,我可以像下面这个编写代码:
在上面的语句中,我在LINQ查询语句中使用了一个“Where”子句以返回属于特定Category的Products。我使用CategoryId来进行筛选。
LINQ TO SQL的优点之一是在定义如何查询数据的时候具有很大的灵活性,而且我还可以利用我在建立LINQ TO SQL数据类时建立的关系对数据库进行更加丰富和常见的查询。举个例子,我可以将这个查询修改成根据Product的CategoryName来实行,而不是像上面那样根据CategoryId,就像下面这样。
注意上面,我是如何使用Product的“ Category”属性去筛选Products,这些Products都属于拥有特定CategoryName的Category。这个属性由LINQ TO SQL自动为我们创建,因为我们在为Category和Product类建模的时候它们之间在数据库中拥有一对多的关系。
为了举一个在查询中使用我们数据模型的相联关系的例子,我们可以使用下面的LINQ查询语法,以获得那些有5个或者更多订单的Products。
注意上面,我们是如何使用LINQ TO SQL为我们在Product类上创建的“OrderDetails”集合(这是因为我们在使用LINQ TO SQL设计器时会有一个一对多关系)。
在调试时显示LINQ TO SQL的实际查询代码
当你在对你的对象进行查询或更新的时候,LINQ TO SQL对象关系映射器会自动进行创建、执行合适的SQL代码的操作。
开发者对于这种新的ORM最关心或者最恐惧的一个之一问题是“实际执行的是什么SQL代码?”。当你在调试你的应用程序时,LINQ一个非常好的特性就是它可以非常容易地查看实际执行的究竟是什么SQL代码。
从Visual Studio 2008的Beta2版本以后,你可以使用新的插件程序LINQ TO SQL查看器容易的查看(以及测试)任何LINQ TO SQL查询表达式。简单的在LINQ TO SQL查询上设置一个断点,然后单击放大图标来开启调试器的表达式查看器。
通过这样,你会看到一个对话框,上面显示了LINQ TO SQL在执行获取Product对象操作时的实际SQL代码:
如果你在这个对话框上点击“Execute”按钮,将会使用调试器直接执行这段SQL代码,并且看到从数据库中返回的实际结果集。
很显然,这样将非常容易准确地知道LINQ TO SQL为你执行的是什么SQL语句。值得注意到是在你需要做些改动的时候,你可以有选择的覆盖这个LINQ TO SQL所执行的SQL语句 – 尽管在98%的情况下,我想你会发现LINQ TO SQL所将执行的SQL代码是非常、非常好的。
绑定LINQ TO SQL查询到Asp.Net控件
LINQ 查询返回实现了Ienumerable接口的结果,这个接口也是Asp.Net服务器控件支持对象绑定的接口。这就意味着,你可以绑定任何LINQ,LINQ TO SQL,或者LINQ TO XML查询的结果到任何的Asp.Net控件。
举个例子,你可以像下面这样在.aspx页面中声明一个<asp:gridview>的控件,
然后,可以像下面这样绑定我们已经写好的LINQ TO SQL查询的结果到GridView中:
这样会产生像下面这样的一个页面:
构造我们的查询结果
现在当我们运行我们的Product查询时,默认地,我们将获取所有所需列的数据以填充Product实体类。
举个例子,这个查询获取Products:
结果是所有的数据都被返回了。
通常,我们仅仅想要返回每个Product数据的一个子集。我们可以使用新的、LINQ 和新C#/VB编译器所支持的数据构造特性去指明我们仅仅想要一个数据的子集,这个可以通过像下面这样修改我们的LINQ TO SQL查询来完成。
这样从我们的数据库将仅返回这些数据(如同我们的debug查看器所显示的)。
LINQ TO SQL比较酷的地方是:在构造数据的时候,可以完全利用数据模型类的联系。这就使我可以写出非常有用(也非常高效)的数据查询。举个例子,下面的查询获取来自Product实体的ID和Name,此Product下的订单的总数,然后结算每个Product的订单的税金总和。
上面“Revenue”属性右侧的表达式是使用由LINQ所提供的“Sum”扩展方法的一个例子。它使用一个返回每个产品订单项的值的 Lambada 表达式 作为一个参数。
LINQ TO SQL是非常智能的,并可以将上面的LINQ表达式在求值时 转换成下面的 SQL语法(如果我们的debug查看器所显示的)。
上面的SQL将会在SQL Server上对所有的NumOrders和Revenue的值进行计算,并仅从数据库获取下面所显示的数据(这使得它运行得非常快)。
我们可以将查询结果绑定到Gridview控件上以产生一个不错的用户界面。
顺便说一下,以免你不断猜测,当你在写这些LINQ构造查询时,你可以在VS2008中获得完全的智能提示。
在上面的例子中,我声明了一个使用对象初始化去构造和定义结果结构的匿名类型。真正酷的地方是:在同这些匿名结果序列打交道的时候,VS2008同样提供了完全的智能提示、编译检测和refactoring支持。
对查询结果进行分页
Web环境中最常见的一个需求就是高效地对用户界面层数据进行分页。LINQ 对两个使分页又简单又高效的扩展方法提供内置的支持,这两个方法是 Skip() 和 Take()。
我们可以使用下面的Skip()和Take()方法,指明我们仅想返回10个Product对象 – 从我们定义的作为参数的初始Product行开始。
注意上面,我没有将Skip()和Take()添加到初始的Products查询声明上,而是在后面添加到查询上(当绑定到GridView数据源时)。常有人问我“这样不是说查询首先从数据库中返回所有的数据,然后在(不好的)中间层中再进行分页?”不会的。原因是LINQ使用一种延迟执行模型 – 这就意味着查询并不实际的执行,直到你试图遍历结果的时候。
这种延迟执行模型的好处之一是,它可以让你在代码语句间非常好地进行查询组合(这样就提高了代码可读性)。它同样可以使你从其他查询中组合查询 – 这样就使一些非常灵活的查询组合和重用的情况成为可能。
一旦我定义好上面的BindProduct()方法,我可以在页面中写出下面的代码以获得来自Querystring的起始索引,并让gridview中的Products分页显示。
这样将呈现给我们一个Products页面,筛选出那些有5个或者以上订单的Products,动态显示计算的Product数据,并且可以通过querystring参数来进行分页。
注意:当使用SQL 2005进行开发的时候,LINQ TO SQL将使用ROW_NUMBER() 函数来进行数据库中的所有分页逻辑。这就确保了在执行上面的代码时,当前显示页只有10行我们所需要的数据从数据库中返回。
这就使得对大型数据序列进行分页的时候更加高效和容易。
总结
对于LINQ TO SQL提供的一些很酷的数据查询方式,希望上面的这些步骤能提供一个好的概览。想要学习更多的LINQ表达式和由VS2008中的C#、VB编译器所支持的新语言语法,请阅读下面这些我更早时发布的随笔:
l 自动属性,对象初始化器和集合初始化器
l 扩展方法
l Lamabada表达式
l 查询语法
l 匿名类型
在我LINQ TO SQL系列的下一篇随笔中,我将介绍我们如何清楚地对我们的数据模型类添加验证逻辑,并且演示我们如何使用它封装业务层逻辑,这些业务逻辑在每次我们进行更新、插入或者删除数据的时候都会运行。然后我将解释更高级的Lazy和eager加载查询场景(Lazy and eager loading query scenarios),如何使用新的<asp:LINQDataSource>控件来支持Asp.Net控件的声明式数据绑定,乐观并发错误处理,以及其他更多内容。