在这篇文章中,我希望澄清三组概念,它们是:
- AJAX / AJAX框架 / AJAX.NET (Professional) / ASP.NET AJAX
- LINQ / LINQ to SQL / LINQ to XXX
- Lambda Expression / Expression Tree / 匿名方法
AJAX / AJAX框架 / AJAX.NET (Professional) / ASP.NET AJAX
这是我见过的混淆最多的一组概念。不过当去年AJAX技术如火如荼,AJAX框架层出不穷,而微软又推出ASP.NET AJAX框架之后,这四个概念之间的混淆也愈发严重起来了。我经常能看到这样的问题:
- 我在用AJAX技术时不知道怎么在UpdatePanel里……
- 我用AJAX.NET框架调用Web Service时……
这是非常典型的概念混淆,上面两句话的AJAX与AJAX.NET都应该使用ASP.NET AJAX替换。关于这几个概念的区别我《深入浅出ASP.NET AJAX》系列WebCast多次进行解释,不过收效似乎并不明显。于是我打算在这里再进行一下说明:
AJAX是Asynchronous JavaScript and XML缩写。这个概念代表的是一种技术,当您在说“我在项目中使用了AJAX技术时”,只是代表了您使用客户端XMLHttpRequest对象与服务器端进行异步通信。不过因为随着AJAX技术的运用往往会带来丰富的客户端效果,因此对AJAX技术的广义理解也可以认为这是一种操作客户端DOM而带来丰富效果的技术(这个“广义”的描述其实并不准确,大家明白老赵的意思就可以了)。
AJAX框架是一套辅助开发人员使用AJAX技术的代码包(库,etc.)。一个AJAX框架的目的一般是对XMLHttpRequest对象的使用进行封装,并提供一些操作DOM元素或者实现特殊效果的“捷径”。成熟的AJAX框架有很多,它们可以被分为“以客户端为中心”和“面向特定服务器技术”两大类。前者的优秀代表有(排名不分先后):Prototype、jQuery、Mootools、YUI、Dojo等等(最后两个其实更接近一套客户端界面库,也就是“广义”的AJAX框架);而后者的典型代表既是AJAX.NET和ASP.NET AJAX了——当然,也有优秀的AJAX框架面向其他服务器技术,在此不提。
AJAX.NET (Professional)是ASP.NET平台上著名的AJAX框架,诞生于2005年2月(虽然正式命名为AJAX.NET则是在两个月之后),作者为Michael Schwarz。同年10月,Michael为AJAX.NET提供了更丰富的功能(主要是安全性方面),并将其改名为AJAX.NET Professional。AJAX.NET提供的最主要的(也是唯一)的功能就是异步调用服务器端方法,可谓非常纯粹的“基于数据”的AJAX使用方式。这个框架是一个个人作品,有支持.NET 1.1和2.0的版本,它并不属于微软官方,目前已经停止更新。
ASP.NET AJAX的Code Name为“Atlas”,在CTP向Beta版转移时曾经发生过翻天覆地的变化。ASP.NET AJAX中包含了UpdatePanel等控件,可以非常透明地为现有的ASP.NET WebForms应用程序添加AJAX效果。此外还提供了客户端异步调用Web Services的方法,使开发人员也能够使用面向数据的方式使用AJAX技术。值得一提的是ASP.NET AJAX的“附属品”相当丰富。例如ASP.NET AJAX名为“Microsoft AJAX Library”的客户端部分是一个纯客户端AJAX框架,提供了面向对象类型系统、浏览器兼容层、异步通信层等多种基础组件;ASP.NET AJAX的开源扩展包“AJAX Control Toolkit”包含了数十个可以直接使用的AJAX服务器端控件,这样开发人员能够轻松地添加丰富的客户端效果。同时,官方还为ASP.NET AJAX提供了“非正式”地扩展包,其中的History等优秀控件也将加入未来版本的ASP.NET AJAX框架中。ASP.NET AJAX是官方出品的AJAX框架,目前已经被集成到ASP.NET 3.5中去了,因此其版本号也从ASP.NET AJAX 1.0一下子“跃升为”ASP.NET AJAX 3.5。如果您看到了这些版本号也请不要疑惑,其实ASP.NET AJAX 3.5相对于ASP.NET AJAX 1.0来说只是修补了一些细小bug,几乎没有任何变化。
LINQ / LINQ to SQL / LINQ to XXX
LINQ是新生事物,不过从不少文章和讨论上看来,这方面的概念也已经有点混沌不清了。因此我们经常可以看到这样的话:
- LINQ只能将数据表与实体属性一一对应……
- LINQ开发指南:在LINQ中进行数据库字段映射……
以上两句话其实说的都是LINQ to SQL而不是指LINQ。可能由于LINQ to SQL的上镜率最广(连MSDN上About LINQ的第一个示例就是查询数据库的),因此许多人都将LINQ to SQL与LINQ混用,这会给初学者造成误解,认为LINQ就是LINQ to SQL,LINQ to SQL就是LINQ——事实当然不是这样的。
LINQ是Language-Integrated Query的缩写,是C# 3.0和VB 9.0中新加入的语言特性,可以在编程时使用内置的查询语言进行基于集合的操作。这么做可以大大简化开发过程,提高开发效率。例如:
List<User> userList = GetUserList();
var userWithOddId = from u in userList
where u.UserID % 2 == 1
select u;
foreach (User u in userWithOddId)
{
Console.WriteLine(u.UserName);
}
如果没有LINQ,要筛选出ID为奇数的User对象则需要创建一个List,然后遍历整个列表,将符合特定条件的User对象放入新列表。而有了LINQ,这部分的筛选就变得非常容易,甚至只需要一句话就能完成。如果觉得这个例子不够说明LINQ对生产力有重大贡献的话,请关注我接下来的一篇文章(暂定名为《我们为什么要拥抱LINQ》)。LINQ特指形如上面这段代码中from...where...select这样的用法,其返回值是IQueryable<T>或IEnumerable<T>。
LINQ to SQL是.NET 3.5内置的一个轻量级O/R Mapping解决方案,可以将数据表映射为实体对象,方便开发人员对数据库的操作。可见,LINQ to SQL实只是LINQ的一个实现,提供了一个可以查询SQL Server数据库的LINQ Provider。
LINQ Provider是LINQ查询的执行器,标准LINQ语法支持许多的操作符,但是某个具体的LINQ实现可能只支持其中的一部分。在.NET 3.5默认提供了三种LINQ Provider,分别是LINQ to Object(即上面的例子),LINQ to SQL以及LINQ to XML。
LINQ to XXX表示使用LINQ针对XXX这种数据进行查询的解决方案。我们可以自定义LINQ Provider,使用我们自定义的查询规则来处理特定数据集。目前互联网上已经可以找到数十种LINQ Provider(如LINQ to Flickr,LINQ to NHibernate等),而已经处于beta 3阶段的ADO.NET Entity Framework,最终也会提供一个LINQ Provider,叫做“LINQ to Entities”。
Lambda Expression / Expression Tree / 匿名方法
Lambda Expression从定义上讲是指带有“=>”符号的表达式,例如:
- x => x + 1
- (x, y) => x > y
- () => 5
- (x, y) => { return x > y; }
Lambda Expreesion本身并不会在概念上引起混淆,不过由于在C# 3.0中Lambda Expression有两种截然不同的使用方式,有些朋友就会产生疑问,究竟Lambda Expression是做什么用的?
Lambda Expression的一个重要作用就是提供一种使用匿名方法的新语法,在《您善于使用匿名函数吗?》一文中您可以看到这种使用方式。利用Lambda Expression表示匿名函数的一个重要的缺点就是无法使用带out或ref关键字的参数,不过它比使用delegate关键字的表示法略为简单一点,因为无需提供参数类型,例如:
public static bool CallMethod(Func<int, bool> method)
{
return method(0);
}
static void Main(string[] args)
{
CallMethod(delegate(int a) { return false; });
CallMethod(a => { return false; });
}
可见,使用Lambda Expression表示的匿名方法无需指定参数类型,因为这一切都已经交给编译器来判断了。这一点在参数类型长而复杂的情况下(例如并行库中的方法)非常重要。因此现在要不是会涉及到out/ref参数,我都会使用Lambda Expression来表示匿名方法。
Lambda Expression的另一个作用自然就是构造一个LambdaExpression对象。任意一个Expression对象都表示了一个Expression Tree的根节点,而开发人员可以通过解析这个Expression Tree来实现特定的功能。我们编写的方法可以接受一个Lambda Expression作为参数,但是我们还必须对这个参数的形式进行限制,这个参数才能有意义。这时候我们就会使用Expression<TDelegate>类型作为方法的参数类型,这样在使用这个方法时就必须使用满足TDelegate的签名及返回值的Lambda Expression才能编译通过。例如:
public static void CallMethod(Expression<Func<int, bool>> prediect) { ... }
static void Main(string[] args)
{
CallMethod(a => a > 0);
}
解析一个Expression Tree并不是一件简单的事情,一定程度上这相当于在进行编译工作,只是最终生成的结果不是机器码或IL,而是一个执行结果,并且语法解析的过程已经由C#编译器帮我们完成了。我在《扩展LINQ to SQL:使用Lambda Expression批量删除数据》一文中曾经提到过这一点,并且给出了一个实例,感兴趣的朋友们可以参考一下。
需要注意的是,如果在构造一个Expression对象时,Lambda Expression的Body部分不能是Statement;而在表示一个匿名对象时Lambda Expression的Body既可以是Expression也可以是Statement。无论是使用Expression还是Statement作为Body,只要表示的含义相同,编译器都会生成一样的匿名函数。例如以下两种写法其实是等价的:
- (x, y) => x > y
- (x, y) => { return (x > y); }
最后,我留给大家一个问题:以下两个做法的结果是相同的,而代码也非常接近。不过它们其实有着非常大的区别,您能指出吗?
var intList = new List<int>() { 1, 2, 3, 4, 5 };
foreach (int i in intList.Where(i => i % 2 == 1))
{
Console.WriteLine(i);
}
var intList = new List<int>() { 1, 2, 3, 4, 5 }.AsQueryable();
foreach (int i in intList.Where(i => i % 2 == 1))
{
Console.WriteLine(i);
}
在写LINQ语句的时候,往往会看到.AsEnumerable() 和 .AsQueryable() 。
例如:
string strcon ="Data Source=.\\SQLEXPRESS;Initial Catalog=Db_Example;Persist Security Info=True;User ID=sa;Password=sa";
SqlConnection con =new SqlConnection(strcon);
con.Open();
string strsql ="select * from SC,Course where SC.Cno=Course.Cno";
SqlDataAdapter da =new SqlDataAdapter(strsql,con);
DataSet ds =new DataSet();
da.Fill(ds, "mytable");
DataTable tables=ds.Tables["mytable"]; //创建表
var dslp = from d in tables.AsEnumerable() select d;//执行LINQ语句,这里的.AsEnumerable()是延迟发生,不会立即执行,实际上什么都没有发生
foreach(var res in dslp)
{
Response.Write(res.Field<string>("Cname").ToString());
}
上述代码使用LINQ 针对数据集中的数据进行筛选和整理,同样能够以一种面向对象的思想进行数据集中数据的筛选。在使用LINQ 进行数据集操作时,LINQ 不能直接从数据集对象中查询,因为数据集对象不支持LINQ 查询,所以需要使用AsEnumerable 方法返回一个泛型的对象以支持LINQ 的查询操作。
.AsEnumerable()是延迟执行的,实际上什么都没有发生,当真正使用对象的时候(例如调用:First, Single, ToList....的时候)才执行。
下面就是.AsEnumerable()与相对应的.AsQueryable()的区别:
AsEnumerable将一个序列向上转换为一个IEnumerable, 强制将Enumerable类下面的查询操作符绑定到后续的子查询当中。
AsQueryable将一个序列向下转换为一个IQueryable, 它生成了一个本地查询的IQueryable包装。
- .AsEnumerable()延迟执行,不会立即执行。当你调用.AsEnumerable()的时候,实际上什么都没有发生。
- .ToList()立即执行
- 当你需要操作结果的时候,用.ToList(),否则,如果仅仅是用来查询不需要进一步使用结果集,并可以延迟执行,就用.AsEnumerable()/IEnumerable /IQueryable
- .AsEnumerable()虽然延迟执行,但还是访问数据库,而.ToList()直接取得结果放在内存中。比如我们需要显示两个部门的员工时,部门可以先取出放置在List中,然后再依次取出各个部门的员工,这时访问的效率要高一些,因为不需要每次都访问数据库去取出部门。
- IQueryable实现了IEnumberable接口。但IEnumerable<T> 换成IQueryable<T>后速度提高很多。原因:
- IQueryable接口与IEnumberable接口的区别: IEnumerable<T> 泛型类在调用自己的SKip 和 Take 等扩展方法之前数据就已经加载在本地内存里了,而IQueryable<T> 是将Skip ,take 这些方法表达式翻译成T-SQL语句之后再向SQL服务器发送命令,它并不是把所有数据都加载到内存里来才进行条件过滤。
- IEnumerable跑的是Linq to Object,强制从数据库中读取所有数据到内存先。