Linq的底层原理探讨
前言
有一篇文章ABP-引入SqlSugar很多人都在催促我,出下一章因为工作忙一直没写。现在开第二节课Linq的底层原理探讨。一起探讨完,看看有没有引起SqlSugar的新思路。
这文章叫linq的底层原理。从哪里开始说呢?Linq To SQL、Linq To Objects、Linq To XML 、Linq To List等等linq可以对很多数据集进行操作。但是linq是怎么能做到的呢,我就想是不是从linq 转换成 sql语句入手就可以说明了。
首先要探讨原理,你必须要有点前置知识
一.委托
先了解什么是委托,我们继续往下讲
简单理解委托可以是:我把我宝贵逻辑托付给别人来执行
下面的linq里面常用的 p => p.Name == "王清培" 写法,其实就是委托
Student[] StudentArrary = new Student[3]{new Student() { Name = "王清培", Age = 24, Sex = "男", Address = "江苏南京",Id=1 },new Student() { Name = "陈玉和", Age = 23, Sex = "女", Address = "江苏盐城",Id=2 },new Student() { Name = "金源", Age = 22, Sex = "女", Address = "江苏淮安" ,Id=3}};//-------------------匿名方法----------------//泛型委托 = 匿名函数Func<Student, bool> student1 = p => p.Name == "王清培"; var resultListOne = StudentArrary.Where(p => p.Name == "王清培");//等价var resultList1 = StudentArrary.Where(student1);
二.前置知识2--树
树是一种数据结构,学技算机的都听过
简单解释下,上面为二叉树,其实就是一种特殊的数据结构。其逻辑是。父节点记录 存储左子节点与右子节点。每个左右子节点也存储了其 左右子节点。
我的源码有一份二叉树的实现代码,感兴趣可以去看看,我旧不展开说了。里面已经实现了增删查改
有了前置知识后我们开始看linq的源码
//-------------------匿名方法----------------//泛型委托 = 匿名函数Func<Student, bool> student1 = p => p.Name == "王清培";
//前面委托的时候介绍到他们2个方法是等价的。得到的结果是一样的var resultListOne = StudentArrary.Where(p => p.Name == "王清培");//等价var resultList1 = StudentArrary.Where(student1);
换一种类型更好的编译,效果是一样的
1.首先因为IEnumerable类型不好 反编译 所以我改下类型,所以我把IEnumerable类型改为IQueryable类型 (不去深究为什么了)
var StudentArraryIQ = StudentArrary.AsQueryable();
//等价与上一页是等价的IQueryable 接收 Expression类型的委托,用了转换
Expression<Func<Student, bool>> student2 = p => p.Name == "王清培"; var resultList2 = StudentArraryIQ.Where(student2);
我上工具,使用 ILSpy 进行反编译。你会发现他们编译出来的都是一串一样的未知代码。
其实这串代码,就算Linq的底层原理---表达式树
我整理代码,大概理解下作用
1.整理下代码。拆分出来好观察(我有美化的成分,但是都能对的上,我专门一行一行理清的)public static Expression<Func<Student, bool>> ZDYTreeList(){//p 指向 对象StudentParameterExpression p = Expression.Parameter(typeof(Student), "p");//定义一个常量 "王清培"var stringWQP = Expression.Constant("王清培", typeof(string));//属性Namevar nameExp = typeof(Student).GetProperty("Name");//p.Namevar pageExpl = Expression.Property(p, nameExp);/** //p.Name.ToString() 详细请看下面 ZDYTreeList2var toString = typeof(string).GetMethod("ToString", new Type[0] );var pageExplToString=Expression.Call(pageExpl, toString);*///p.Name=="王清培" GreaterThan大于 LessThan小于var pageWQPAndExpl = Expression.Equal(pageExpl, stringWQP);//执行p => p.Name == "王清培"Expression<Func<Student, bool>> studentLI = Expression.Lambda<Func<Student, bool>>(pageWQPAndExpl, new ParameterExpression[1] { p });return studentLI;}
最好假如我直接用表达式树去运行都是一样的,等价
//等价var resultList4 = StudentArraryIQ.Where(ZDYTreeList());
表达式树转SQl
由此我可以证明了 linq的底层代码就是 表达式树。那么表达式树是怎么与数据库关联的呢?
数据库语句是不懂什么叫表达式树的。它只知道sql语句
Expression<Func<Student, bool>> expressoin = p => p.Id == 1 && p.Name == "王清培" && p.Age == 24;
//如果是要对应数据库对应的sql
//select * from Student where id=1 and name='王清培' and age=24
现在我们再重新解剖 得到的表达式expressoin的body
BinaryExpression be = expressoin.Body as BinaryExpression; //为什么叫表达式树,那肯定因为他是一个树型结构var beNodeType = be.NodeType;//树的顶点var beNodeLeft = be.Left.ToString();//左节点var beNodeRight = be.Right;//右节点var beNodeRight2 = be.Left;//右节点的左节点
竟然如此,用最笨的方法,怎么把 得到的节点数据转化成sql语句?
这些代码是不是就是我前面提到的树。
如果是用人脑,或者很笨的方式,把左右节点每一个过一遍,用正则表达式,加上各种的判断逻辑,最终肯定可以编译出sql语句。Linq封装了一个解析工具 ExpressionVisitor,但是他是一个抽象方法,一些关键方法我们要自己实现下。因为给你一套解剖工具,你用来解剖汽车,还是解剖机器人用法都是不一样的 。大概的一个树型结构
相信大家脑海里都已经50%了解,怎么实现了。只是动手没办法实现而已,具体可以看ExpressionVisitor,它
(1) Visit拆分二元表达式--》
(2)
VisitMember 解析二元表达式 把里面的方法函数 专成 sql的函数方法
VisitBinary解析二元表达式 把对应的NodeType转成 对应的sql 运算符
VisitConstant解析二元表达式 把里面的方法产量转换
VisitMethodCall解析二元表达式 转换未方法表达式
(3)VisitBinary重新递归Visit 里的左右节点
/// <summary>/// 如果是二元表达式/// </summary>/// <param name="node"></param>/// <returns></returns>protected override Expression VisitBinary(BinaryExpression node){if (node == null) throw new ArgumentNullException("BinaryExpression");this._StringStack.Push(")");base.Visit(node.Right);//解析右边//节点翻译成 sql字符串this._StringStack.Push(" " + node.NodeType.ToSqlOperator() + " ");base.Visit(node.Left);//解析左边this._StringStack.Push("(");return node;}
internal static string ToSqlOperator(this ExpressionType type){switch (type){case (ExpressionType.AndAlso):case (ExpressionType.And):return "AND";case (ExpressionType.OrElse):case (ExpressionType.Or):return "OR";case (ExpressionType.Not):return "NOT";case (ExpressionType.NotEqual):return "<>";case ExpressionType.GreaterThan:return ">";case ExpressionType.GreaterThanOrEqual:return ">=";case ExpressionType.LessThan:return "<";case ExpressionType.LessThanOrEqual:return "<=";case (ExpressionType.Equal):return "=";default:throw new Exception("不支持该方法");}}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;namespace LinqToList
{public class ConditionBuilderVisitor : ExpressionVisitor{private Stack<string> _StringStack = new Stack<string>();public string Condition(){string condition = string.Concat(this._StringStack.ToArray());this._StringStack.Clear();return condition;}/// <summary>/// 如果是二元表达式 /// </summary>/// <param name="node"></param>/// <returns></returns>protected override Expression VisitBinary(BinaryExpression node){if (node == null) throw new ArgumentNullException("BinaryExpression");this._StringStack.Push(")");base.Visit(node.Right);//解析右边this._StringStack.Push(" " + node.NodeType.ToSqlOperator() + " ");base.Visit(node.Left);//解析左边this._StringStack.Push("(");return node;}/// <summary>/// /// </summary>/// <param name="node"></param>/// <returns></returns>protected override Expression VisitMember(MemberExpression node){if (node == null) throw new ArgumentNullException("MemberExpression");this._StringStack.Push(" [" + node.Member.Name + "] ");return node;}/// <summary>/// 常量表达式/// </summary>/// <param name="node"></param>/// <returns></returns>protected override Expression VisitConstant(ConstantExpression node){if (node == null) throw new ArgumentNullException("ConstantExpression");this._StringStack.Push(" '" + node.Value + "' ");return node;}/// <summary>/// 方法表达式/// </summary>/// <param name="m"></param>/// <returns></returns>protected override Expression VisitMethodCall(MethodCallExpression m){if (m == null) throw new ArgumentNullException("MethodCallExpression");string format;switch (m.Method.Name){case "StartsWith":format = "({0} LIKE {1}+'%')";break;case "Contains":format = "({0} LIKE '%'+{1}+'%')";break;case "EndsWith":format = "({0} LIKE '%'+{1})";break;case "Equals":format = "({0} == {1})";break;default:format = "";break;// throw new NotSupportedException(m.NodeType + " is not supported!");}this.Visit(m.Object);this.Visit(m.Arguments[0]);string right = this._StringStack.Pop();string left = this._StringStack.Pop();this._StringStack.Push(String.Format(format, left, right));return m;}}
}
最后输出sql语句
//1 解析节点var node = vistor.Visit(expressoin);var sqlWhere =vistor.Condition();var sqlNew= "SELECT * FROM where"+ vistor.Condition();
和平时用的不像?
可能有人说,虽然有点略懂,但和平时的不像啊。是不是我瞎编的?
那我就继续用 IOrderedQueryable 写一个自定义的底层的IRepository(ABP框架对IRepository还做了很多很多封装,我只是简单实现了下底层转Sql的部分)。我起名为AomiQuery
//初始化 1
AomiQuery<Products> aomiProducts = new AomiQuery<Products>();var query = from p in aomiProducts where p.ProductID > 1 select p;
List<Products> proList = query.ToList();foreach (Products p in proList)
{Console.WriteLine("ProductID:{0} ----------------> ProductName:{1}", p.ProductID, p.ProductName);
}Console.ReadKey();
上面的代码可以看到,除了 AomiQuery外 其他的都和平时的写法一样的。我已经实现了数据库的对接和数据库查询。并且除了安装了数据连接工具(ADO.net),没有安装任何的第三方框架。你们平时之所以能写linq查询数据是用ef框架。
那其实我的 AomiQuery 是做了绿色框的步骤
接下来的话,只能意会了,听起来可能有点烧脑
关键点是在这句话
var query = from p in aomiProducts where p.ProductID > 1 select p;
(1)(LInq底层封装给接口的逻辑 3-1)
会进入AomiQueryProvider 的 CreateQuery,因为我继承 :IQueryProvider<T>所做底层封装
(2)(LInq底层封装给接口的逻辑 3-2)
CreateQuery把表达式树传个 AomiQuery,并且把自己也带过去
public AomiQuery(Expression expression, IQueryProvider provider) 传入了表达式树
(3)
Tolist 会触发 AomiQuery类的执行GetEnumerator--触发--》AomiQueryProvider 的Execute方法--触发--》ExecuteReader 解析GetEnumerator表达式树 expression
贴上解析代码(自定义了数据连接,数据执行)
//8 返回查询的实体数据public object ExecuteReader(Expression expression, bool isEnumerable = false){if (expression is MethodCallExpression){//应该是个继承关系 MethodCallExpression ExpressionMethodCallExpression mce = expression as MethodCallExpression;#region 得到数据库链接SqlConnection connection = new SqlConnection("Server=192.168.1.197,49307; Database=Abp5TestDb; Uid=sa; Pwd=maike123!@#+1s;MultipleActiveResultSets=true;");SqlCommand command = new SqlCommand();command.Connection = connection;#endregionStringBuilder commandText = new StringBuilder();if (mce != null && mce.Method.DeclaringType == typeof(Queryable) && mce.Method.Name == "Where"){commandText.Append("SELECT * FROM ");ConstantExpression ce = mce.Arguments[0] as ConstantExpression;IQueryable queryable = ce.Value as IQueryable;commandText.Append(queryable.ElementType.Name);commandText.Append(" WHERE ");UnaryExpression ue = mce.Arguments[1] as UnaryExpression;LambdaExpression lambda = ue.Operand as LambdaExpression;BinaryExpression be = lambda.Body as BinaryExpression;MemberExpression lme = be.Left as MemberExpression;ConstantExpression rce = be.Right as ConstantExpression;commandText.Append(lme.Member.Name);switch (be.NodeType){case ExpressionType.And:commandText.Append(" AND ");break;case ExpressionType.Or:commandText.Append(" OR ");break;case ExpressionType.Equal:commandText.Append(" = ");break;case ExpressionType.NotEqual:commandText.Append(" <> ");break;case ExpressionType.LessThan:commandText.Append(" < ");break;case ExpressionType.LessThanOrEqual:commandText.Append(" <= ");break;case ExpressionType.GreaterThan:commandText.Append(" > ");break;case ExpressionType.GreaterThanOrEqual:commandText.Append(" >= ");break;}commandText.Append(rce.Value);}command.CommandText = commandText.ToString();List<Products> proList = new List<Products>();#region 打卡数据库读取数据加载到内存connection.Open();SqlDataReader dr = command.ExecuteReader();while (dr.Read()){//加载到内存 ListProducts product = new Products();product.ProductID = Convert.ToInt32(dr["ProductID"]);product.ProductName = Convert.ToString(dr["ProductName"]);proList.Add(product);}dr.Close();connection.Close();#endregionreturn isEnumerable ? proList.AsEnumerable() : proList;}return null;}
Linq的底层原理探讨相关推荐
- 深入理解Go底层原理剖析 (送书)
互联网迅猛发展的数十年时间里,不断面领着各种新的场景与挑战,例如大数据.大规模集群计算.更复杂的网络环境.多核处理器引起对于高并发的需求,云计算,上千万行的服务器代码-- 那些成熟但上了年纪的语言没能 ...
- 分布式事务之底层原理揭秘
, hi 大家好,今天分享一这篇文章,让大家彻底了解分布式原理,这个是后台开发必须掌握技能. 刚性事务 柔性事务 本地事务 分布式事务 单阶段原子提交协议 两阶段提交协议 定义 原理 性能 恢复 缺陷 ...
- 『Go 语言底层原理剖析』文末送书
互联网迅猛发展的数十年时间里,不断面领着各种新的场景与挑战,例如大数据.大规模集群计算.更复杂的网络环境.多核处理器引起对于高并发的需求,云计算,上千万行的服务器代码-- 那些成熟但上了年纪的语言没能 ...
- 分布式之系统底层原理
作者:allanpan,腾讯 IEG 高级后台工程师 导言 分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持.本文从分布式事务这个概念切入,尝试对分布式 ...
- mysql哨兵机制_Redis 哨兵机制以及底层原理深入解析,这次终于搞清楚了
前面我们基于实际案例搭建了缓存高可用方案(分布式缓存高可用方案,我们都是这么干的)同时提到了redis主从架构下是如何保证高可用的,讲到了它是通过redis sentinel的机制来实现的. 今天我们 ...
- iOS底层原理探究 第一探. 事件传递和响应者链
一. 声明: 本文意在探讨, 也参考了几位大神的文章, 在最后我会把链接发出来, 如果有理解错误的地方, 请大神们指正哈! 二. 前言: 最近自己做项目的时候, 用到了UITabbarContro ...
- 程序员练级攻略(2018):前端基础和底层原理
这个是我订阅 陈皓老师在极客上的专栏<左耳听风>,我整理出来是为了自己方便学习,同时也分享给你们一起学习,当然如果有兴趣,可以去订阅,为了避免广告嫌疑,我这就不多说了!以下第一人称是指陈皓 ...
- 平衡球游戏开发教程(四)--深入了解WP7游戏底层原理
前面我们都只是集中在物理引擎上,但是对于一个游戏来说,他包含的不只是物理引擎,他还要有游戏面板(普通的,可以滚动的),按钮,标签,图片,背景(层次背景),边界控制,输入处理,字体和纹理管理,帧率测速器 ...
- 一文彻底搞懂事务底层原理
事务底层原理(INNODB) 前言 redo log 为什么需要 redo log 一些问题 重做日志结构 重做日志文件结构 log group与循环写入 日志何时写入磁盘? 数据恢复:LSN标记 C ...
最新文章
- C#中将dll汇入exe,并加壳
- 优雅的在React项目中使用Redux
- 第130天:移动端-rem布局
- Py之pydotplus:pydotplus的简介、安装、使用方法之详细攻略
- OS_CORE.C(总结)
- 阿里云中间件首席架构师李小平:云原生实践助力企业高效创新
- 过年回家抢不到火车票?教你用 Python 开发 12306 查票神器
- Python module模块 包 __name__
- HDU 2574 HDOJ 2574 Hdu Girls' Day ACM 2574 IN HDU
- java学习(4):第一个java程序
- 小android模拟器,小姚Android模拟器工作室版本v6.2.7.0正式版
- 张文宏又爆“金句”:上班开会,要和关系最差的人坐一起……
- 单因素方差分析graphpad_【SPSS】单因素方差分析(比较均值gt;单因素ANOVA)
- Java基础知识强化之集合框架笔记50:Map集合之Map集合的概述和特点
- No module named ‘pyqt5‘解决办法
- ORB-SLAM2代码详解
- c语言实现各种排序算法(作业:点名册排序)
- ICC2使用report_placement检查floorplan
- 程序实现启用/禁用设备(驱动)enable/disable device with windows api
- 网络中常说的“丢包”是什么?—Vecloud微云