本文发表在 rolia.net 枫下论坛我贴的这些都是我平时的学习笔记,过一段时间这些知识会忘记,我希望在我还清醒的时候把这些玩意以尽量通俗易懂的方式记录下来,以备不测:
Linq 有两种查询,完全按照资料翻译就是“本地查询”Local Query 和“翻译查询”Interpreted Query,听着很令人困惑,没办法,英文原文如此。这两种查询很多程序员往往不加区别的使用,在大多数情况下没有任何问题,两种查询中所暴露出来的使用方法是一模一样的。就像我开QQ车和开Toyota车,暴露给驾驶员的开车方法没有任何不同,都是油门刹车,但是QQ直接把刹车力度的大小传递到刹车鼓上,而Toyota会把刹车力度大小首先送给内部计算机,计算机“翻译”一下,再决定是应该自动爆冲还是立即刹车。Linq的2种查询有些像我说的这个例子
半年多前我在使用Linq查询的时候遇到了一个异常错误,在解决问题的时候理解了这两种查询的不同之处,我出错的程序是这样的:
我有一个对数据库表操作的(Linq to SQL)查询,表名是student, fields 分别是
create table student
(
name varchar(20),
age int,
address varchar(20)
)
在C#中,这个表的定义类是这样写的:
[Table]
public student
{
[Column]
public string name;
[Column]
public int age;
[Column]
public string address;
public student(string n) { name = n; }
}
我要找出所有 name 符合某种Regular Express的student,理想的写法应该是这样的
main()
{
DataContext dc = new DataContext("connection string");
Table<student> students = dc.GetTable<student>();
// 找出所有name以k开头,以p结尾的student
var result = students.Where(s=>Regex.IsMatch(s.name,"^k.*p$"));
foreach(student s in result) Console.WriteLine(s);
}
程序运行时出错,提示T-SQL没有相应的函数,我把程序改成这个样子
// 找出所有name以k开头,以p结尾的student
var result = students.Where(s=>s.name.StartWith("k") && s.name.EndWith("p"));
程序运行正常,呵呵,区别原来在这里
student.Where(....)返回的是这样一个东西
IQueryable<student> result = students.Where(s=>Regex.IsMatch(s.name,"^k.*p$"));
一旦返回值是IQueryable<T>,那么这样的查询就会被称为是“翻译查询”。“本地查询”所返回的值永远是IEnumerable<T>,比如这个例子
var result = (new student[] {new student("Bush"),new student("Tom"),new student("kacp")}).Where(s=>Regex.IsMatch(s.name,"^k.*p$"));
同样的类型,同样的student,只不过我在本地把它封装成一个数组,在用Where查询的时候,它的返回值就是一个IEnumerable<student>的值,完整的写出是这个样子:
IEnumerable<student> result = (new student[] {new student("Bush"),new student("Tom"),new student("kacp")}).Where(s=>Regex.IsMatch(s.name,"^k.*p$"));
这个查询就是所谓的“本地查询”,而且这个本地查询不会出错,正则表达试运行得非常好
看看区别,首先从语法定义上看:
本地查询:public static IEnumerable<TSource> Where<TSource> (this
IEnumerable<TSource> source, Func<TSource,bool> predicate)
翻译查询:public static IQueryable<TSource> Where<TSource> (this
IQueryable<TSource> source, Expression<Func<TSource,bool>> predicate)
最大的区别在于
本地查询:Func<TSource,bool> predicate 和
翻译查询:Expression<Func<TSource,bool>> predicate
本地查询的表达式会在编译的时候直接被编译器编译成委托变成.dll或者.exe加以运行,但是翻译查询的表达式首先会变成一个“表达式树”(Expression<T>)静静的躺在程序里面,直到运行时这个“表达式树”才会被“翻译”成对象能够理解的“查询”(比如T-SQL,或者在Entity Framework里是一种特殊的对Entity Data Model进行查询的SQL)加以运行,是不折不扣地“动态”运行,这也就是为什么我的Linq to SQL 的 Where查询在编译的时候不会出错,因为编译器不理会你要做什么,只是帮助你检查你的C#“查询”语法有没有错误,而这个查询到底能不能用直到运行时才会知道。
那么是谁负责“翻译”我的“表达式树 查询”呢?在我的例子中,是DataContext类,它里面有一套程序专门负责在运行时把我传递给它的“表达式树 查询”[在这个例子中是:.Where(s=>Regex.IsMatch(s.name,"^k.*p$")]翻译成数据库能够听得懂的语言,当然就是T-SQL了,然后运行这个T-SQL,在把返回结果变成IQueryable<T>的形式返回给.net。
现在我的这个错误就很好理解了,因为T-SQL中没有Regular Express的操作,所以DataContext无法把我的查询请求翻译成T-SQL语言,于是就会报错。而我的另外一种查询
students.Where(s=>s.name.StartWith("k") && s.name.EndWith("p"));
DataContext里面有相应的功能把它们翻译成T-SQL运行,所以就运行正常啦(我想理论上微软如果愿意肯定也能支持正则表达式,可能LINQ to SQL team的这些家伙太懒,不愿意写吧!)
如果用正常的比较方法,例如用 EndWith, StartWith, >,<,==, != ,以及他们的组合仍然无法满足要求(比如选择出所有符合e_mail pattern的string,就只能用证则表达式了),那就没有办法了,只能全部选择,然后在.net环境里面运用C#的灵活性进行二次选择了。如下:
DataContext dc = new DataContext("connection string");
Table<student> students = dc.GetTable<student>();
var result = students.AsEnumerable().Where(s=>Regex.IsMatch(s.name,"^k.*p$"));
在这里必须加上AsEnumerable(),通过这个AsEnumerable()就把 IQueryable<student>的对象变成了IEnumerable<student>,这个时候再进行后面的Where查询的时候就是玩的“本地查询”了,而不是“翻译查询”,当然,正则表达式也可以正常运行了
---------------
一些题外话:
到目前为止,微软官方发布的实现了IQueryable<TSource> 的产品只有 Linq to SQL 和Linq to Entity Framework。 对 Entity Framework的查询更加不一样,linq to sql是直接对sql server数据库查询,也就是说linq to sql 会把用户的查询翻译成sql server能够听得懂的语言-- T-SQL。
但是使用linq to entity framework查询datasource的时候是首先对一个中间层,叫Entity Data Model(EDM)进行查询,也就是说linq to entity framework会首先把用户的查询翻译成EDM能够听得懂的语言(好像叫SQL for EF),然后后面又有一套程序负责根据EDM的状态进行T-SQL查询。
民间有很多Ling to 的项目,比如 Linq to Google, Linq to Orcacle,等等,所有这些项目本质上就是实现这个IQueryable<TSource> 接口
因为IQueryable<TSource>继承自IEnumerable<T>,所以不关心后台运作的程序员不会感觉到这两者有什么区别,我也是Linq to SQL出了一次错才开始研究IQueryable<TSource>到底是什么玩意
rolia藏龙卧虎,小弟纯粹是闲得没事班门弄斧,如果以上哪里有理解不对的地方,还忘各位大佬指出,大家共同和谐进步更多精彩文章及讨论,请光临枫下论坛 rolia.net
Linq 有两种查询,完全按照资料翻译就是“本地查询”Local Query 和“翻译查询”Interpreted Query,听着很令人困惑,没办法,英文原文如此。这两种查询很多程序员往往不加区别的使用,在大多数情况下没有任何问题,两种查询中所暴露出来的使用方法是一模一样的。就像我开QQ车和开Toyota车,暴露给驾驶员的开车方法没有任何不同,都是油门刹车,但是QQ直接把刹车力度的大小传递到刹车鼓上,而Toyota会把刹车力度大小首先送给内部计算机,计算机“翻译”一下,再决定是应该自动爆冲还是立即刹车。Linq的2种查询有些像我说的这个例子
半年多前我在使用Linq查询的时候遇到了一个异常错误,在解决问题的时候理解了这两种查询的不同之处,我出错的程序是这样的:
我有一个对数据库表操作的(Linq to SQL)查询,表名是student, fields 分别是
create table student
(
name varchar(20),
age int,
address varchar(20)
)
在C#中,这个表的定义类是这样写的:
[Table]
public student
{
[Column]
public string name;
[Column]
public int age;
[Column]
public string address;
public student(string n) { name = n; }
}
我要找出所有 name 符合某种Regular Express的student,理想的写法应该是这样的
main()
{
DataContext dc = new DataContext("connection string");
Table<student> students = dc.GetTable<student>();
// 找出所有name以k开头,以p结尾的student
var result = students.Where(s=>Regex.IsMatch(s.name,"^k.*p$"));
foreach(student s in result) Console.WriteLine(s);
}
程序运行时出错,提示T-SQL没有相应的函数,我把程序改成这个样子
// 找出所有name以k开头,以p结尾的student
var result = students.Where(s=>s.name.StartWith("k") && s.name.EndWith("p"));
程序运行正常,呵呵,区别原来在这里
student.Where(....)返回的是这样一个东西
IQueryable<student> result = students.Where(s=>Regex.IsMatch(s.name,"^k.*p$"));
一旦返回值是IQueryable<T>,那么这样的查询就会被称为是“翻译查询”。“本地查询”所返回的值永远是IEnumerable<T>,比如这个例子
var result = (new student[] {new student("Bush"),new student("Tom"),new student("kacp")}).Where(s=>Regex.IsMatch(s.name,"^k.*p$"));
同样的类型,同样的student,只不过我在本地把它封装成一个数组,在用Where查询的时候,它的返回值就是一个IEnumerable<student>的值,完整的写出是这个样子:
IEnumerable<student> result = (new student[] {new student("Bush"),new student("Tom"),new student("kacp")}).Where(s=>Regex.IsMatch(s.name,"^k.*p$"));
这个查询就是所谓的“本地查询”,而且这个本地查询不会出错,正则表达试运行得非常好
看看区别,首先从语法定义上看:
本地查询:public static IEnumerable<TSource> Where<TSource> (this
IEnumerable<TSource> source, Func<TSource,bool> predicate)
翻译查询:public static IQueryable<TSource> Where<TSource> (this
IQueryable<TSource> source, Expression<Func<TSource,bool>> predicate)
最大的区别在于
本地查询:Func<TSource,bool> predicate 和
翻译查询:Expression<Func<TSource,bool>> predicate
本地查询的表达式会在编译的时候直接被编译器编译成委托变成.dll或者.exe加以运行,但是翻译查询的表达式首先会变成一个“表达式树”(Expression<T>)静静的躺在程序里面,直到运行时这个“表达式树”才会被“翻译”成对象能够理解的“查询”(比如T-SQL,或者在Entity Framework里是一种特殊的对Entity Data Model进行查询的SQL)加以运行,是不折不扣地“动态”运行,这也就是为什么我的Linq to SQL 的 Where查询在编译的时候不会出错,因为编译器不理会你要做什么,只是帮助你检查你的C#“查询”语法有没有错误,而这个查询到底能不能用直到运行时才会知道。
那么是谁负责“翻译”我的“表达式树 查询”呢?在我的例子中,是DataContext类,它里面有一套程序专门负责在运行时把我传递给它的“表达式树 查询”[在这个例子中是:.Where(s=>Regex.IsMatch(s.name,"^k.*p$")]翻译成数据库能够听得懂的语言,当然就是T-SQL了,然后运行这个T-SQL,在把返回结果变成IQueryable<T>的形式返回给.net。
现在我的这个错误就很好理解了,因为T-SQL中没有Regular Express的操作,所以DataContext无法把我的查询请求翻译成T-SQL语言,于是就会报错。而我的另外一种查询
students.Where(s=>s.name.StartWith("k") && s.name.EndWith("p"));
DataContext里面有相应的功能把它们翻译成T-SQL运行,所以就运行正常啦(我想理论上微软如果愿意肯定也能支持正则表达式,可能LINQ to SQL team的这些家伙太懒,不愿意写吧!)
如果用正常的比较方法,例如用 EndWith, StartWith, >,<,==, != ,以及他们的组合仍然无法满足要求(比如选择出所有符合e_mail pattern的string,就只能用证则表达式了),那就没有办法了,只能全部选择,然后在.net环境里面运用C#的灵活性进行二次选择了。如下:
DataContext dc = new DataContext("connection string");
Table<student> students = dc.GetTable<student>();
var result = students.AsEnumerable().Where(s=>Regex.IsMatch(s.name,"^k.*p$"));
在这里必须加上AsEnumerable(),通过这个AsEnumerable()就把 IQueryable<student>的对象变成了IEnumerable<student>,这个时候再进行后面的Where查询的时候就是玩的“本地查询”了,而不是“翻译查询”,当然,正则表达式也可以正常运行了
---------------
一些题外话:
到目前为止,微软官方发布的实现了IQueryable<TSource> 的产品只有 Linq to SQL 和Linq to Entity Framework。 对 Entity Framework的查询更加不一样,linq to sql是直接对sql server数据库查询,也就是说linq to sql 会把用户的查询翻译成sql server能够听得懂的语言-- T-SQL。
但是使用linq to entity framework查询datasource的时候是首先对一个中间层,叫Entity Data Model(EDM)进行查询,也就是说linq to entity framework会首先把用户的查询翻译成EDM能够听得懂的语言(好像叫SQL for EF),然后后面又有一套程序负责根据EDM的状态进行T-SQL查询。
民间有很多Ling to 的项目,比如 Linq to Google, Linq to Orcacle,等等,所有这些项目本质上就是实现这个IQueryable<TSource> 接口
因为IQueryable<TSource>继承自IEnumerable<T>,所以不关心后台运作的程序员不会感觉到这两者有什么区别,我也是Linq to SQL出了一次错才开始研究IQueryable<TSource>到底是什么玩意
rolia藏龙卧虎,小弟纯粹是闲得没事班门弄斧,如果以上哪里有理解不对的地方,还忘各位大佬指出,大家共同和谐进步更多精彩文章及讨论,请光临枫下论坛 rolia.net