.NET深入解析LINQ框架(三:LINQ优雅的前奏)

2019-03-01 17:11:44 浏览数 (1)

1】.动态LINQ查询(动态构建Expression<T>表达式树)

什么是动态LINQ查询?LINQ的编写是静态的,因为C#是基于静态类型系统原理设计的,在编写时已经确定类型,也就是在编译时就已经知道将要执行什么样的查询,条件是什么、排序方式是什么等等。那么很大一部分应用场合中我们需要根据用户的选择来查询数据源,以往我们都是通过判断的方式来拼接查询的SQL字符串,但是现在我们面对是强类型的LINQ查询,是否可以很方便的进行类似查询。其实也没有什么好神秘的,基本的实现原理是通过动态的构建表达式树来实现IQueryable<T>接口的查询。 其实动态LINQ查询所能执行的最关键的因素在于Expression<T>对象是可以被动态编译成可以执行的委托对象,委托对象是完全可以被直接使用的可执行代码段,这就为动态LINQ查询提供了基础。对于IEnumerable<T>类型的查询表达式方法都知道它的执行是不会直接接受Expression<T>类型对象的,那么动态LINQ是否能工作于IEnumerable<T>接口?其实可以的,有个很隐蔽的窍门隐藏在IQueryable<T>扩展方法对象Queryable中,也就是AsQueryable<T>方法,它返回的是一个实现了IQueryable<T>接口的EnumerableQuery对象,该对象的实现内容不是很复杂,将动态拼接的数据结构Expression<T>对象编译成可以执行的匿名函数,然后直接执行查询。 我们来看一下EnumerableQuery对象的重点,它肯定有一个地方是将Expression<T>对象Compiler的地方。

代码语言:javascript复制
 1 private IEnumerator<T> GetEnumerator()
 2         {
 3             if (this.enumerable == null)
 4             {
 5                 EnumerableRewriter rewriter = new EnumerableRewriter();
 6                 Expression<Func<IEnumerable<T>>> expression2 =
 7                     Expression.Lambda<Func<IEnumerable<T>>>(rewriter.Visit(this.expression), (IEnumerable<ParameterExpression>)null);
 8                 this.enumerable = expression2.Compile()(); //(1)重点      
 9             }
10             return this.enumerable.GetEnumerator();
11         }

在上述代码中的“(1)重点”的地方,我们很清楚的看见表达式树被动态编译后然后紧接着又被执行,这里就能看出为什么IEnumerable<T>对象需要能够被转换成IQueryable<T>对象。这样就可以消除IEnumerable<T>、IQueryable<T>这两个接口之间的动态查询瓶颈。

为什么需要动态LINQ查询,上面说过问题出在我们没办法在运行时再去编写Lambda表达式了,都知道Lambda表达式到最后就是被编译成Expression表达式树对象,所以我们可以在运行时自己动态的构建Expression对象,这样就可以将动态构建出来的表达式树对象直接传入到需要的方法中。如果查询的数据对象是IEnumerable<T>则会被动态编译成可以执行的委托然后直接执行,如果查询的是IQueryable<T>则顺其自然的被提供程序解析执行。

下面我们来看一个简单的动态查询例子:

代码语言:javascript复制
1  //构造Student数组
2             Student[] StudentArrary = new Student[3]
3             {
4                 new Student(){Name="王清培", Age=24, Sex="男", Address="江苏南京"},
5                 new Student(){Name="陈玉和", Age=23, Sex="女", Address="江苏盐城"},
6                 new Student(){Name="金源", Age=22, Sex="女", Address="江苏淮安"}
7             };

这是一组数据,为了简单测试就不搞那么麻烦的Linq to Sql数据源了。我们将要通过动态的构建表达式树来做为查询的逻辑,以往我们的Lambda在这个时候派不上用场了,在运行时我们无法再去构建委托类型。

现在的需求是从界面上接受一个Name值的输入,LINQ的查询只需要直接写就行了。

代码语言:javascript复制
1 var list = from i in StudentArrary where i.Name == "王清培" select i; 

但是我们需要动态的构建表达式树来执行查询,表达式树的任何一个节点都有相对应的Expression派生类型,所以我们只要将相关类型组装起来就行了。由于我建的示例程序的类型是控制台程序,所以我们就用简短的方式演示一下如何构建表达式树。

代码语言:javascript复制
 1 ParameterExpression parameter = Expression.Parameter(typeof(Student), "stu");//表示二元运算符的左边参数名称
 2             //表示"stu"参数的"stu.Name"中的Name属性,Name属性必须是反射获取的元数据才行,这样框架就才可以找到它
 3             MemberExpression property = Expression.MakeMemberAccess(parameter, typeof(Student).GetMember("Name")[0]);
 4             //表示常量值
 5             Console.WriteLine("请输入要查询人的名称:");
 6             ConstantExpression name = Expression.Constant(Console.ReadLine());//从用户输入流中读取值
 7             BinaryExpression binary = Expression.MakeBinary(ExpressionType.Equal, property, name);//拼接==运算符的左边、右边
 8             //完整的表达式是Lambda才对
 9             LambdaExpression lambda = Expression.Lambda(binary, parameter);
10             //重要的就在这里,我们将完整的Lambda表达式直接变成可以执行的委托
11             Func<Student, bool> wheredelegate = lambda.Compile() as Func<Student, bool>;
12             //将编译后的可执行委托直接放入Where方法中执行
13             var list2 = StudentArrary.AsQueryable<Student>().Where(wheredelegate);
14             foreach (var i in list2)
15             {
16                 Console.WriteLine("查询列表:");
17                 Console.WriteLine("姓名:{0},年龄:{1},地址:{2}", i.Name, i.Age, i.Address);
18             }
19             Console.ReadLine();

图例:

该例子的重点是如何动态构建逻辑,根据不同的项目要求完全可以将类似的功能封装起来供以后重复使用。如果觉得手动编写表达式树很麻烦的话,建议可以找一个辅助类能将Lambda表达式的对象树都能打印出来的工具,然后对着这棵树在去写就简单多了。

关于动态LINQ的第三方的API不是很多,比较常用的就是Dynamic.cs的使用,具体我没有用过,看过相关文档应该还是比较方便的。它的内部原理其实还是动态的构建表达式树,只不过这部分工作被人家做了,而我们使用起来却简单的很多。

2】.DLR动态语言运行时(基于CLR之上的动态语言运行时)

从C#1一路走来,它变的越来越强大,.NET平台变得无所不能。很多人还一直咬着.NET不能跨平台,不能支持动态对象,不支持非托管等等理由来排斥它,然而他们所不知的是.NET已经悄无声息的做出来一大举动,那就是在静态语言运行时上嵌入动态语言运行时环境。我想不是微软不能支持所谓的缺点,而是它确实有它的本意。

动态语言运行时是在.NET4.0中引入的建立在CLR之上的运行时环境,目的是为了在静态语言中能够借鉴动态语言运行时的优点,比如强大的类型随意变换,这点在设计应用开发框架时尤其重要,任何一个好的特性都需要大面积的使用模式才能变的更完美。

说到动态运行时就不得不提JS中让人兴奋的var定义的对象特性了,如果没有留意在设计框架时而存在的烦恼其实很难发现动态运行和静态语言之间的好与不好。很明显的例子就是当我们定义一个数据类型的对象时,无法再在后期运行时对它进行其他类型的使用,看一个简单的例子:

代码语言:javascript复制
1 dynamic obj = 1;//整形  
2 obj = "1";//字符串 
3 obj = new { Name = "王清培", Age = 24, Address = "江苏" };//匿名对象类型 

在运行时我们可以随意的设计对象的类型,我大胆的假设完全可以用动态运行时特性设计类似人工智能系统,提供基本原型,然后根据用户自己的思维方式构建任意对象树。技术科研是很不错的方向,企业应用可能还有待商讨。

以往我们很难在运行时为对象动态的添加属性、行为、事件,通过动态语言运行时我们可以很自如的添加想要的东西。

下面我们来看一个简单的例子,在运行时动态的构建一个对象类型,在以前我们只有用动态编译、CodeDom技术来实现,这里将变的很简单。

代码语言:javascript复制
 1 static void Main(string[] args)
 2         {
 3             dynamic objModel = new ExpandoObject();//初始化可以动态添加属性、方法、事件的ExpandoObject对象
 4             objModel.Name = "王清培";//设置属性值
 5             objModel.Age = 24;
 6             objModel.WriteEvent = null;//存放事件的委托字段定义
 7             objModel.WriteEvent  = new Action<string>(WriteName);//设置事件的方法
 8             objModel.WriteEvent(objModel.Name   objModel.Age);
 9             Console.ReadLine();
10         }
11         public static void WriteName(string info)
12         {
13             Console.WriteLine(info);
14         }

一个很简单的例子告诉我们可以在C#中去编写如JS中的动态对象功能,不过目前还不是很成熟,动态对象的成员没有智能提示,应该是还没有被大面积使用起来,以后肯定也是一大美餐;

总结:LINQ框架的基本使用原理就全部结束了,后面我们就来学习如何能让LINQ查询我们自定义的数据源。很多朋友都喜欢自己写ORM框架,那么你肯定少不了对LINQ的支持吧?后面我们就来详细的讲解如何扩展IQueryable<T>、IQueryableProvider<T>两个重量级接口,只有他们两个才能让我们和LINQ对话,这两个接口还是很神秘的。

0 人点赞