先给关注dlinq 的朋友们道歉,最近工作实在忙,没有时间来写blog 。从本节开始,我们讲dlinq 语法咯。我们先从select 子句讲起。看下面的例子。
var q = from c in db.Customers select c.ContactName;
这是一个最简单的dlinq 查询语句,查询得到联系人的名字。在这里,我需要提醒下大家的是,像这个语句只是个声明,dlinq 并没有真正把数据取出来,只有当你需要该数据的时候,它才会帮你去取,这就是延迟加载(deferred loading) 。如果,你想在声明的时候就希望dlinq 帮你取到数据,你可以使用ToList() 或ToArray() 方法。如上例。
var q = (from c in db.Customers select c.ContactName).ToArray();
或
var q = (from c in db.Customers select c.ContactName).ToList();
在这里,我还要提醒大家一点。dlinq 返回的结果集是对象的集合,不是数据的。
在dlinq 执行的时候,它会先将上面的标准查询转换成dlinq 的API (也有人叫级连方法), 比如,下面语句
var q = from c in db.Customers where c.City == "London" select c;
就会先被转化成 var q = db.Customers.Where(c=>c.City== "London").Select(c=>c); 也就是说,这两个语句是等价的。而后,dlinq 会解析影射文件,根据dlinq 的query 语句,自动产生sql 语句,并把sql 送到sql server 服务器,根据返回的数据集,创建相应的对象。在这里,你可能会对c=>c 感到非常陌生。这是Lambda 表达式(expression ),你可以理解c 为结果集里的任一对象,这对象的类型是和你结果集里元素类型是一致的 。这里理解起来可能困难。我们一起来理解下数据即是对象的概念。我相信这会帮我们理解Lambda 表达式。
在dlinq 之前,在java 领域有Hibernate ,在net 领域有NHibernate 技术,来实现object/relational 持久和查询服务。dlinq 其实质上,是在吸收了众多技术的基础上,比他们更加强大的工具。数据即对象的含义有两层。第一,数据结构(表结构)即是类。可以描述为Table Schema--Class 。第二,表里的数据即是变量,描述为Data--object(variable) 。那么,我们在来理解Lambda 表达式可能就容易些。刚才我们已经说了,var q = db.Customers.Where(c=>c.City== "London").Select(c=>c); 将会返回Customers 对象的集合,也就说,这个集合的每个元素就是一个Customer 。Lambda 表达式是对c# 2.0 中的anonymous methods (匿名方法)的扩展。它更加简化匿名方法的实现形式。这里的c 是一种隐式的声明,编译器会自动推断它的实际类型,也可以显示声明,比如, var q = db.Customers.Where((Customer c) => c.City == "London").ToList(); Lambda 表达式用=> 符号跟随一个表达式,这个表达式,需要返回一个类型,其实质就是一个方法返回一个类型。它只是更加简洁的匿名方法。然后,where 等操作符用它返回的这个类型做为参数。关于Lambda 表达式的具体实现,我会在进阶部分详细讲解。这里不再赘述。
有一点要提醒大家的是,标准的查询语句,必须是select 语句在最后,而级连表达式,各种操作符的位置并不是很重要。比如var q = db.Customers.Where(c=>c.City== "London").Select(c=>c); 可以写成var q = db.Customers.Select(c=>c).Where(c=>c.City== "London"); 它们两个是一样的,但是,标准查询就不可以换位子,select 语句必须在最后。虽然在级连表达式,各种操作符的位置并不是很重要,但是他们还是有区别的。特别是在使用匿名类后,区别很明显。但万变不离其宗,我们只要记住,下一个操作符总是在上一个操作符所筛选的数据集的基础上进行筛选。这点,我会在以后的blog 中,更加详细的说明。
在select 语句中,另一个难点是匿名类。比如列子
var q = from c in db.Customers select new {c.ContactName, c.Phone};
其实不光在select 操作中有匿名类,其他操作中也有。让我们一起来理解下匿名类。上面的语句与
var q = db.Customers.Select(c=>new {c,ContactName,c.Phone}); 是等价的。匿名类是c# 3.0 中新出现的特性。其实质是编译器根据用户定义,自动产生一个匿名的类,帮用户实现临时变量的储存。注意,是临时变量。大量使用匿名类会使程序可读性降低。匿名类还依赖于另外一个特性,就是在c# 3.0 可以支持根据property 来创建对象。比如,有类
public class Person { private string name; public string Name { get { return name; } set { name = value; } } }
以前,我们只可以用构造函数来创建其对象,现在在3.0 中支持用property 来创建,即,可以用
var d = new Person { Name = "s" }; 来创建对象。在这里,你可能还对var 类型产生疑问。你可能以为c#3.0 和javascript 一样是弱类型的。其实var 并不是c#3.0 的类型,它是编译器的关键字,编译器根据实际变量的返回类型,自动推断类型。那么var c = null; 是无法编译通过,因为编译不知道null 代表那个类型。所以,c#3.0 还是强类型的。
现在3.0 可以支持用property 来创建对象了,那么就有了匿名类的出现。比如,var d = new { Name = "http://www.my400800.cn" }; 编译器自动产生一个有property 叫做Name 的匿名类,然后按这个类型分配内存,并初始化对象。在这个地方,还有个问题,比如,var d = new { " http://www.my400800.cn " }; 是编译不通过的。因为,编译器不知道匿名类中的property 的名字。但是,如果, string c = "d"; var d = new { c}; 则是可以通过编译的。编译器会创建一个叫做匿名类带有叫c 的property 。
在dlinq 中,比如new {c,ContactName,c.Phone}); 这里出现ContactName 和Phone 都是我们在影射文件中定义的和表中字段相对应的property 。编译器在取会数据并创建对象时,会创建一个匿名类,这个类有两个属性,为ContactName 和Phone ,然后根据数据初始化对象。匿名类还有另外一种形式。
var q = from e in db.Employees select new {Name = e.FirstName + " " + e.LastName, Phone = e.HomePhone};
这种形式和第一种不同的是,编译器会重命名property 的名字。当然也可以把两种形式组合起来。
var q = from p in db.Products select new {p.ProductID, HalfPrice = p.UnitPrice / 2};
第一个属性的名字不会变,第二个会被重新命名。
好,就先讲这几个,下节我会介绍几个更复杂的用法。