Fork me on GitHub

.NET深入解析LINQ框架(六:LINQ执行表达式)

阅读目录:

  • 1.LINQ执行表达式

在看本篇文章之前我假设您已经具备我之前分析的一些原理知识,因为这章所要讲的内容是建立在之前的一系列知识点之上的,为了保证您的阅读顺利建议您先阅读本人的LINQ系列文章的前几篇或者您已经具备比较深入的LINQ原理知识体系,防止耽误您的宝贵时间。

到目前为止我们对LINQ的执行原理已经很清楚了,从它的前期构想到它真正为我们所用都有足够的证据,但是似乎问题并没有我们想的那么简单,问题总是在我们使用中频频出现尤其是新技术的使用,当然有问题才能有进步。

一:LINQ执行表达式

在研究LINQ的过程中,参考了很多技术文章还有技术书籍,毫无疑问的是Linq to Provider的调用入口都是将Lambda表达式解析成Expression<T>表达式对象,跟Linq to Object不同,Linq to Object是将Lambda直接解析成泛型Func类型的委托,但是我们很多人包括我自己都忽视了一个很大的细节,就是Provider在内部将对Expression<T>进行执行,并非我们所理解的那样将表达式Expression<T>对象完全解析成等价的SQL,也就是说Expression<T>并不是我们说看到的那样单纯,它具有双重上下文逻辑在里面。

我们都是直接使用LINQ作为查询接口,VS在最后编译的时候负责对LINQ的语法进行解析并且翻译成对应的扩展方法调用。我们忽视一个重要的环节,就是VS对LINQ进行解析翻译的时候是会执行LINQ表达式的,这点非常重要。之前我一直以为VS只负责将LINQ的表达式翻译成等价的扩展方法调用,后来发现VS为了满足我们在前期无法确定对象条件的情况下进行Where字句的拼接,允许我们在编写LINQ语句的时候带有逻辑判断表达式在里面,这个功能对我们进行多条件组合查询时相当方便,不需要在进行IF、ELSE的多个判断,只需要顺其自然的在LINQ中的第一个表达式中进行判断就行了。追求优雅代码的同志很不希望在一个既有LINQ查询又带有链式查询的方法中用两种查询方式,如果LINQ能满足大部分的查询功能那最完美;

为了说明LINQ在编译时会被VS执行,我们用LINQPad工具看一下便知;

LINQ查询表达式:from truck in TB_CX_TRUCKs where 1==1 select truck

LINQ等价的链式方法: TB_CX_TRUCKs.Where (truck => True)

图1:

如果没有执行按道理是直接解析成Lambda的格式(truck)=>1==1才对,然后让LINQ to Provider提供程序负责处理才对,也许觉得没有实质的意思反正是恒等的表达式所以解析成这样。我们在换一种写法看看;

LINQ查询表达式:from truck in TB_CX_TRUCKs where string.IsNullOrEmpty("1111") select truck

LINQ等价的链式方法:TB_CX_TRUCKs.Where (truck => String.IsNullOrEmpty ("1111"))

图2:

由此可以得出一个结论,LINQ语句是会被执行和解析的两个动作,在还没有进入到提供程序时已经可以看出LINQ是可以附带一些执行逻辑在里面的,而不是最终的SQL执行逻辑。

表达式的处理可以分为常量表达式和动态变量表达式,常量表达式在VS编译的时候就可以直接计算表达式是否是true、false。而动态变量表达式则需要在后期进行表达式解析的时候计算的,换句话说Linq to Provider中的Provider提供程序是具有高智商的表达式执行器,不仅仅是对表达式等价解析中间还夹杂着对表达式解析的自定义逻辑代码。

打个比方,我们都有过拼接查询条件的经历,界面上有N个查询条件字段,需要根据用户是否填写了哪个字段进行动态的拼接进LINQ语句中去。一般我们都会进行if的判断才行,因为我们都觉得Where后面的条件表达式是直接被解析成对应逻辑的SQL语句,所以只要拼接进去的都是被解析成SQL的Where子句。由于LINQ是无法拆分开来进行组装的,必须一次写完才能通过编译。所以我们都在使用着查询扩展方法进行数据查询,这样的困境使我们无法看到LINQ的优雅,反而一直用不到。

通过观察LINQPad工具解析的SQL语句,发现LINQ查询表达式在提供程序内部将被执行、解析两个过程,跟VS的过程是一样的,能执行先执行,然后解析,解析是建立在前期执行过后的基础上的。我们还是来看一个比较简单的LINQ解析后的SQL和链式方法;

LINQ查询表达式:from truck in TB_CX_TRUCKs where 1==1 ||truck.LICENSE_NUMBER.Length<10 select truck

LINQ等价的链式方法:TB_CX_TRUCKs.Where (truck => (True || (truck.LICENSE_NUMBER.Length < 10)))

图3:

对照链式方法,很明显VS先对1==1表达式进行了执行并返回true作为后面整个表达式的一部分拼接进Where链式方法,所以先执行再解析两个过程。然后我们对最后的SQL进行分析,没有看见任何Where语句,为什么呢?是因为提供程序在内部对表达式进行了执行并分析了我们想要的输出结果,也不知道这样的效果是不是为了满足我们多条件拼接的问题。

由于Where方法里面的Lambda表达如果被执行的话,那么将不会执行(truck.LICENSE-NUMBER.Length<10),所以这点为我们的多条件拼接提供了接口。

我们看一下多条件组合查询示例:

将界面上的查询实体传入到数据访问层之后:

View Code
 1 public List<Truck> GetList(Truck truckModel) 
 2 { 
 3     using (KJtest0817Entities DbContext = new KJtest0817Entities()) 
 4     { 
 5         var resultList = from truck in DbContext.TB_CX_TRUCK 
 6                          where string.IsNullOrEmpty(truckModel.ENGINE_NUMBER) || truck.ENGINE_NUMBER == truckModel.ENGINE_NUMBER 
 7                          where string.IsNullOrEmpty(truckModel.LICENSE_NUMBER) || truck.ENGINE_NUMBER == truckModel.LICENSE_NUMBER 
 8                          select new Truck() 
 9                          { 
10                              BRAND = truck.BRAND 
11                          }; 
12         return resultList.ToList(); 
13     } 
14 }

这样的查询LINQ确实很优美,比起之前的IFELSE判断也省事很多。

View Code
1 IQueryable<TB_CX_TRUCK> queryList = DbContext.TB_CX_TRUCK.AsQueryable();//初始化一个IQueryable对象 
2 if (!string.IsNullOrEmpty(truckModel.LICENSE_NUMBER)) 
3        queryList = queryList.Where(truck => truck.LICENSE_NUMBER.Contains(truckModel.LICENSE_NUMBER)); 
4 if (!string.IsNullOrEmpty(truckModel.TRUCK_MODEL_CODE)) 
5        queryList = queryList.Where(truck => truck.TRUCK_MODEL_CODE.Contains(truckModel.TRUCK_MODEL_CODE));

如果有很多个查询条件,那么我们将要写很多这样的判断代码,即不方便也不美观。

(注:查看大图)

多条件之间的OR查询

尽管很多场合下我们都是使用Linq中的where关键字来拼接查询条件,但是有一种需求Linq查询确实满足不了我们,那就是多条件之间是OR的关系。因为只要我们用Linq或者链式方法出来的写出来的SQL语句中的where条件后面将都是and关系,这个时候我们只能用链式方法来进行拆分才行。

View Code
 1 public List<DutyModel> GetList(DutyModel dutyModel) 
 2 { 
 3     using (UserOrgDemo2Entities Context = new UserOrgDemo2Entities()) 
 4     { 
 5         IQueryable<TB_DUTY> result = Context.TB_DUTY.AsQueryable(); 
 6         System.Linq.Expressions.Expression<Func<TB_DUTY, bool>> expressionDUTY = DynamicLinqExpressions.True<TB_DUTY>(); 
 7 
 8         if (!(dutyModel.RANK_NO == 0)) 
 9             expressionDUTY.Or(duty => duty.RANK_NO == dutyModel.RANK_NO); 
10         if (!string.IsNullOrEmpty(dutyModel.USER_PRIV)) 
11             expressionDUTY.Or(duty => duty.USER_PRIV == dutyModel.USER_PRIV); 
12 
13        return result.Where(expressionDUTY).Select(tb_duty=>new DutyModel(){ USER_PRIV=tb_duty.USER_PRIV}).ToList(); 
14     } 
15 }

这里有个重点就是老外(估计是比较厉害的前辈,在此谢谢了!)写的一个*.cs文件,里面是Expression<T>表达式文件的扩展方法,主要就是用来进行多条件Or、And之间组合查询用的。

所有说如果多条件组合查询之间是and关系可以直接使用Linq,如果是or或者是or与and一起,那么可以使用上面这种链式查询方法。

总结:其实说了那么多目的只有一个,LINQ的解析过程并非只有一个“提供程序翻译成SQL”的过程,而是包括了两个阶段,四个过程的处理,LINQ的写法很多种,原理应该是差不多的,只要我们在写LINQ的时候综合考虑这几个处理过程,应该对我们应对复杂的查询很有帮助。

 

posted @ 2013-02-05 16:06  王清培  阅读(8194)  评论(18编辑  收藏  举报