前言

有一篇文章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 student1 = p => p.Name == "王清培";

var resultListOne = StudentArrary.Where(p => p.Name == "王清培");

//等价

var resultList1 = StudentArrary.Where(student1);

二.前置知识2--树

树是一种数据结构,学技算机的都听过

简单解释下,上面为二叉树,其实就是一种特殊的数据结构。其逻辑是。父节点记录 存储左子节点与右子节点。每个左右子节点也存储了其 左右子节点。

我的源码有一份二叉树的实现代码,感兴趣可以去看看,我旧不展开说了。里面已经实现了增删查改

有了前置知识后我们开始看linq的源码

//-------------------匿名方法----------------

//泛型委托 = 匿名函数

Func 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> student2 = p => p.Name == "王清培";

var resultList2 = StudentArraryIQ.Where(student2);

我上工具,使用 ILSpy 进行反编译。你会发现他们编译出来的都是一串一样的未知代码。

其实这串代码,就算Linq的底层原理---表达式树

我整理代码,大概理解下作用

1.整理下代码。拆分出来好观察(我有美化的成分,但是都能对的上,我专门一行一行理清的)

public static Expression> 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> studentLI = Expression.Lambda>(pageWQPAndExpl, new ParameterExpression[1] { p });

return studentLI;

}

最好假如我直接用表达式树去运行都是一样的,等价

//等价

var resultList4 = StudentArraryIQ.Where(ZDYTreeList());

表达式树转SQl

由此我可以证明了 linq的底层代码就是 表达式树。那么表达式树是怎么与数据库关联的呢?

数据库语句是不懂什么叫表达式树的。它只知道sql语句

Expression> 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 里的左右节点

///

/// 如果是二元表达式

///

///

///

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 _StringStack = new Stack();

public string Condition()

{

string condition = string.Concat(this._StringStack.ToArray());

this._StringStack.Clear();

return condition;

}

///

/// 如果是二元表达式

///

///

///

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;

}

///

///

///

///

///

protected override Expression VisitMember(MemberExpression node)

{

if (node == null) throw new ArgumentNullException("MemberExpression");

this._StringStack.Push(" [" + node.Member.Name + "] ");

return node;

}

///

/// 常量表达式

///

///

///

protected override Expression VisitConstant(ConstantExpression node)

{

if (node == null) throw new ArgumentNullException("ConstantExpression");

this._StringStack.Push(" '" + node.Value + "' ");

return node;

}

///

/// 方法表达式

///

///

///

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 aomiProducts = new AomiQuery();

var query = from p in aomiProducts where p.ProductID > 1 select p;

List 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所做底层封装

(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 proList = new List();

#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;

}

查看原文