Linq的底层原理探讨

news2025/1/16 2:53:30

前言

有一篇文章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 指向 对象Student
            ParameterExpression p = Expression.Parameter(typeof(Student), "p");
            //定义一个常量 "王清培"
            var stringWQP = Expression.Constant("王清培", typeof(string));
            //属性Name
            var nameExp = typeof(Student).GetProperty("Name");
            //p.Name
            var pageExpl = Expression.Property(p, nameExp);
            /*
             * //p.Name.ToString() 详细请看下面 ZDYTreeList2
            var 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 Expression
                MethodCallExpression 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;
                #endregion

                StringBuilder 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())
                {
                    //加载到内存 List
                    Products product = new Products();
                    product.ProductID = Convert.ToInt32(dr["ProductID"]);
                    product.ProductName = Convert.ToString(dr["ProductName"]);
                    proList.Add(product);
                }

                dr.Close();
                connection.Close();
                #endregion
                return isEnumerable ? proList.AsEnumerable() : proList;

            }

            return null;
        }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/387616.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机的学习路线

本文是介绍如何成为一个Geek&#xff0c;一个真正的计算机高手。 适合有成为IT领域技术大牛的人参考。 写给大一新生和所有向深耕IT领域的人&#xff0c;避免走一些弯路。 第一门入门的必备功课-语法与算法 什么是计算机&#xff1f; 用来做运算的机器 电子计算机在运算方面…

[1.3_1]计算机系统概述——操作系统的运行机制

文章目录第一章 计算机系统概述操作系统的运行机制前提知识&#xff1a;程序是如何运行的内核程序与应用程序特权指令与非特权指令内核态与用户态小结第一章 计算机系统概述 操作系统的运行机制 操作系统的运行机制&#xff0c;也就是操作系统在计算机上是怎样运行的问题。 操…

Go语言的条件控制语句及循环语句的学习笔记

一、Go的条件控制语句 Go 语言提供了以下几种条件判断语句&#xff1a; 语句描述if 语句if 语句 由一个布尔表达式后紧跟一个或多个语句组成。if…else 语句if 语句 后可以使用可选的 else 语句, else 语句中的表达式在布尔表达式为 false 时执行。if 嵌套语句你可以在 if 或…

C++概览:工具链、基础知识、进阶及总结

本篇写给C初学者&#xff0c;作为概览&#xff0c;文中仅包含各方面基础知识&#xff0c;无深入分析。 C基础概念简介 C编译过程示意图 关键词&#xff1a;源文件、预编译、编译、汇编、链接 C工具链总结 cmake项目工程文件是一种中介工程文件&#xff0c;可以转化成其他…

Python+Qt指纹录入识别考勤系统

PythonQt指纹录入识别考勤系统如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01;前言这篇博客针对<<PythonQt指纹录入识别考勤系统>>编写代码&#xff0c;代码整洁&#xff0c;规则&#xff0c;易读。 学…

如何在 OpenEuler 系统中安装 Docker

Docker 是一种流行的开源容器化平台&#xff0c;它能够将应用程序与其依赖项打包成可移植的容器&#xff0c;从而简化了应用程序的部署和管理。本文将介绍在 OpenEuler 系统中安装 Docker 并使用 Docker 容器控制 5G 模块的具体步骤。 安装 Docker 安装 Docker 的具体步骤如下…

react动态路由组件的封装

react动态路由组件的封装 我这篇比较全面 首先下载包 npm i react-router-dom5 这里为什么要用5的版本为啥不用最新的&#xff0c;原因在于老版本跟新版本写法不一样 老版本 import { HashRouter, Route, Switch, Redirect } from react-router-dom;render() {return (<Ha…

JavaEE——何为线程及创建线程

文章目录一、认识线程1. 线程的概念2. 出现多线程的原因3. 进程与线程4. 对多线程的详细解释二、初次实现多线程代码1. 初步了解2. 使用 Java 中的工具查看当前的所有线程3. Java 中创建线程的多种方式一、认识线程 1. 线程的概念 所谓线程&#xff0c;就是指在一个 ‘执行流…

机器学习、数据挖掘和统计模式识别学习(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 机器学习是让计算机在没有明确编程的情况下采取行动的科学。在过去的十年中&#xff0c;机器学习为我们提供了自动驾驶汽车&…

【2021.12.25】xv6系统入门学习

【2021.12.28】为xv6系统添加一个开机密码 文章目录【2021.12.28】为xv6系统添加一个开机密码0、说明1、Ubuntu20上安装xv62、测试指令3、修改系统代码4、添加自己的程序命令0、说明 xv6 是 MIT 设计的一个教学型操纵系统。 记录Ubuntu上安装x86版本的xv6系统&#xff0c;为其…

Acwing---843. n-皇后问题——DFS

n-皇后问题1.题目2.基本思想3.代码实现1.题目 n−皇后问题是指将 n 个皇后放在 nn 的国际象棋棋盘上&#xff0c;使得皇后不能相互攻击到&#xff0c;即任意两个皇后都不能处于同一行、同一列或同一斜线上。 现在给定整数 n&#xff0c;请你输出所有的满足条件的棋子摆法。 …

ChatGPT介绍以及一些使用案例

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️&#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

AJAX 异步请求详细教程

文章目录AJAX 异步请求简介Jquery 版 Ajax$.ajax() -- Jquery提供的 ajax 函数注册验证用户名是否可用$.get() 与 $.post()Ajax 返回数据类型JSONjson 简介JSON 对象JSON 数组对象数组混合格式小结JSON 应用JSON 对象的使用JSON 数组的使用响应的 json 数组数组对象混合格式Jac…

八大排序算法之堆排序的实现+经典TopK问题

目录 一.堆元素的上下调整接口 1.前言 2.堆元素向上调整算法接口 3.堆元素向下调整算法接口 二.堆排序的实现 1.空间复杂度为O(N)的堆排序(以排升序为例) 思路分析: 代码实现: 排序测试: ​时空复杂度分析: 2. 空间复杂度为O(1)的堆排序(以排降序为例) 将数组arr调…

IGKBoard(imx6ull)-SPI接口编程-回环测试

文章目录1- 使能imx6ull开发板SPI驱动2- 回环测试imx6ull开发板物理连接3- 编程SPI回环测试4- 代码重难点分析&#xff08;1&#xff09;spi_device结构体&#xff08;2&#xff09;spi_ioc_transfer结构体&#xff08;3&#xff09;ioctl函数对于SIP不了解的可以参考这篇文章&…

GVRP-LNP-VCMP讲解

目录 GVRP讲解 动态创建Vlan并将端口加入Vlan GVRP消息类型 GVRP工作原理 LNP讲解 动态修改接口链路类型 VCMP讲解 动态创建Vlan 相关概念 Vlan同步 VCMP与GVRP的区别 GVRP讲解 动态创建Vlan并将端口加入Vlan GVRP&#xff08;GARR Vlan Registration Protocol&#xf…

28个案例问题分析---01---redis没有及时更新问题--Redis

redis没有及时更新问题一&#xff1a;背景介绍二&#xff1a;前期准备pom依赖连接Redis工具类连接mysql工具类三&#xff1a;过程使用redis缓存&#xff0c;缓存用户年龄业务对应流程图使用redis缓存用户年龄对应代码四&#xff1a;总结一&#xff1a;背景介绍 业务中使用redis…

【机器学习面试】百面机器学习笔记和问题总结+扩展面试题

第1章 特征工程 1、为什么需要对数值类型的特征做归一化&#xff1f; &#xff08;1&#xff09;消除量纲&#xff0c;将所有特征统一到一个大致相同的区间范围&#xff0c;使不同指标之间具由可比性&#xff1b; &#xff08;2&#xff09;可以加快梯度下降收敛的速度&#…

powershell-dns-txt远程加载

2022-10-30 参考原文&#xff1a; 远程下载的通用替代方案 &#xff5c; 红队攻防 https://mp.weixin.qq.com/s/9MAKZZfNB5YFT7jgln5lXQ实现过程 dns环境&#xff1a;kali bind9&#xff08;docker版&#xff09;&#xff0c;ip&#xff1a;192.168.161.128 靶机&#xff…

AD封装转Allego Cadence

AD封装转Allego CadenceAD封装转Allego Cadence软件版本转换步骤导出AD文件在PADS导入AD在cadence导入PADS在cadence导出library修改焊盘替换焊盘AD封装转Allego Cadence 有时候在网上下载的封装是AD格式的&#xff0c;但内容实在太多&#xff0c;为了快速便捷获得cadence格式…