抛弃EF,20分构建一个属于自己的ORM框架
发布日期:2021-08-17 08:27:52 浏览次数:19 分类:技术文章

本文共 14637 字,大约阅读时间需要 48 分钟。

相信EF大家都不陌生了,因为数据库表跟程序实体是一一对应的原因,我们能够通过lambda这种函数式的编程方式进行操作数据库,感觉非常清晰明了。与我们直接写SQL相比,lambda是强类型,拥有更好的扩展性,伸缩性,而且编程更加的方便,快捷。。下面我们就基于Expression和lambda来与大家构建一个属于自己的ORM框架。

 

思路的话很简单,就是将lambda转换成我们对应的数据库所需的查询条件,然后执行查询,再将结果以反射的方式封装成List<T>返回出去。

Expression

大家使用EF的时候多多少少会留意到有Expression这个东西。特别是查询时会看到要你传入Expression<Func<T,bool>>这样类型的参数,它又和Func<T,bool>有什么比同呢?

Expression<Func<T,bool>>是表达式树,我们可以通过它来分析我们的委托中的函数。当调用Compile方法后就会变成委托,才能执行。

Func<T,bool>只是一个普通的委托。

例如我们现在有个实体类Staff

public class Staff    {        public string Name { get; set; }        public int Age { get; set; }        public string Code { get; set; }        public DateTime? Birthday { get; set; }        public bool Deletion { get; set; }    }

我们还有一个这样的方法

class Program    {        static void Main(string[] args)        {            FindAs
(x => x.Code == "张三" && x.Name.Contains("张")); } public static List
FindAs
(Expression
> func) { //将func转换成对应数据库的查询条件,然后执行查询 return null;//将结果返回 } }

 

我们希望通过 FindAs<Staff>(x => x.Age <50 && x.Name.Contains("张")); 就能查询出Staff表中Age<50并且Name包含有“张”字的人的信息。而生成的sql语句应该是select * from staff where Age<50 and Name like '%张%'。现在我们就来分析下这个func

 

从上面的图我们可以看到当前的Expression是一个lambda表达式,我们点开它的body看看。

 

 

 

我们可以看到body里分为左边和右边,还有NodeType。和我们的lambda对比下看看'x => x.Code =="张三" && x.Name.Contains("张")'是不是找到点灵感了?我们再继续把左边和右边拆开看看。

 

 

 

可以看到我们需要的信息都有了,看来转换成SQL已经不是什么难事了,动手开搞了。

 

class Program    {        static void Main(string[] args)        {            FindAs
(x => x.Code == "张三" && x.Name.Contains("张")); FindAs
(x => x.Age <= 12 && x.Name.Contains("张")); Console.ReadKey(); } public static List
FindAs
(Expression
> func) { BinaryExpression Binary = func.Body as BinaryExpression; string left = ResovleFunc(Binary.Left); string right = ResovleLinqToObject(Binary.Right); string oper = GetOperator(Binary.NodeType); string sql = string.Format("select * from {0} where {1}", typeof(T).Name, left + oper + right); Console.WriteLine(sql); return null;//将结果返回 } //解析一般的条件,例如x=>x.name==xxxx x.age==xxx public static string ResovleFunc(Expression express) { var inner = express as BinaryExpression; string Name = (inner.Left as MemberExpression).Member.Name; object Value = (inner.Right as ConstantExpression).Value; var Operator = GetOperator(inner.NodeType); string Result = string.Format("({0} {1} '{2}')", Name, Operator, Value); return Result; } //解析linq to object这类扩展方法 public static string ResovleLinqToObject(Expression expression) { var MethodCall = expression as MethodCallExpression; var MethodName = MethodCall.Method.Name; if (MethodName == "Contains") { object Temp_Vale = (MethodCall.Arguments[0] as ConstantExpression).Value; string Value = string.Format("%{0}%", Temp_Vale); string Name = (MethodCall.Object as MemberExpression).Member.Name; string Result = string.Format("{0} like '{1}'", Name, Value); return Result; } return null; } public static string GetOperator(ExpressionType expressiontype) { switch (expressiontype) { case ExpressionType.And: return "and"; case ExpressionType.AndAlso: return "and"; case ExpressionType.Or: return "or"; case ExpressionType.OrElse: return "or"; case ExpressionType.Equal: return "="; case ExpressionType.NotEqual: return "<>"; case ExpressionType.LessThan: return "<"; case ExpressionType.LessThanOrEqual: return "<="; case ExpressionType.GreaterThan: return ">"; case ExpressionType.GreaterThanOrEqual: return ">="; default: throw new Exception(string.Format("不支持{0}此种运算符查找!" + expressiontype)); } } }

 

已经初步的达到了我们的目的了,但是我们的查询条件不可能固定是2个,有可能是N个,这时左边和右边又要继续再分下去,直到无法再分(想到递归了吧?)。而且我们还需要将查询条件参数化。而且我们的条件删除时也会用到。所以我们应该把它独立出来。传入一个lambda,生成sql where部分的语句,生成sqlparameter[]。这才是关键。。于是我们来构建一个解析Expresstion的类。。下面我就直接给出我自己写的实现代码了。。

 

public class ResolveExpress    {        public Dictionary
Argument; public string SqlWhere; public SqlParameter[] Paras; ///
/// 解析lamdba,生成Sql查询条件 /// ///
///
public void ResolveExpression(Expression expression) { this.Argument = new Dictionary
(); this.SqlWhere = Resolve(expression); this.Paras = Argument.Select(x => new SqlParameter(x.Key, x.Value)).ToArray(); } private string Resolve(Expression expression) { if (expression is LambdaExpression) { LambdaExpression lambda = expression as LambdaExpression; expression = lambda.Body; return Resolve(expression); } if (expression is BinaryExpression) { BinaryExpression binary = expression as BinaryExpression; if (binary.Left is MemberExpression && binary.Right is ConstantExpression)//解析x=>x.Name=="123" x.Age==123这类 return ResolveFunc(binary.Left, binary.Right, binary.NodeType); if (binary.Left is MethodCallExpression && binary.Right is ConstantExpression)//解析x=>x.Name.Contains("xxx")==false这类的 { object value = (binary.Right as ConstantExpression).Value; return ResolveLinqToObject(binary.Left, value, binary.NodeType); } if (binary.Left is MemberExpression && binary.Right is MemberExpression)//解析x=>x.Date==DateTime.Now这种 { LambdaExpression lambda = Expression.Lambda(binary.Right); Delegate fn = lambda.Compile(); ConstantExpression value = Expression.Constant(fn.DynamicInvoke(null), binary.Right.Type); return ResolveFunc(binary.Left, value, binary.NodeType); } } if (expression is UnaryExpression) { UnaryExpression unary = expression as UnaryExpression; if (unary.Operand is MethodCallExpression)//解析!x=>x.Name.Contains("xxx")或!array.Contains(x.Name)这类 return ResolveLinqToObject(unary.Operand, false); if (unary.Operand is MemberExpression && unary.NodeType == ExpressionType.Not)//解析x=>!x.isDeletion这样的 { ConstantExpression constant = Expression.Constant(false); return ResolveFunc(unary.Operand, constant, ExpressionType.Equal); } } if (expression is MemberExpression && expression.NodeType == ExpressionType.MemberAccess)//解析x=>x.isDeletion这样的 { MemberExpression member = expression as MemberExpression; ConstantExpression constant = Expression.Constant(true); return ResolveFunc(member, constant, ExpressionType.Equal); } if (expression is MethodCallExpression)//x=>x.Name.Contains("xxx")或array.Contains(x.Name)这类 { MethodCallExpression methodcall = expression as MethodCallExpression; return ResolveLinqToObject(methodcall, true); } var body = expression as BinaryExpression; if (body == null) throw new Exception("无法解析" + expression); var Operator = GetOperator(body.NodeType); var Left = Resolve(body.Left); var Right = Resolve(body.Right); string Result = string.Format("({0} {1} {2})", Left, Operator, Right); return Result; } ///
/// 根据条件生成对应的sql查询操作符 /// ///
///
private string GetOperator(ExpressionType expressiontype) { switch (expressiontype) { case ExpressionType.And: return "and"; case ExpressionType.AndAlso: return "and"; case ExpressionType.Or: return "or"; case ExpressionType.OrElse: return "or"; case ExpressionType.Equal: return "="; case ExpressionType.NotEqual: return "<>"; case ExpressionType.LessThan: return "<"; case ExpressionType.LessThanOrEqual: return "<="; case ExpressionType.GreaterThan: return ">"; case ExpressionType.GreaterThanOrEqual: return ">="; default: throw new Exception(string.Format("不支持{0}此种运算符查找!" + expressiontype)); } } private string ResolveFunc(Expression left, Expression right, ExpressionType expressiontype) { var Name = (left as MemberExpression).Member.Name; var Value = (right as ConstantExpression).Value; var Operator = GetOperator(expressiontype); string CompName = SetArgument(Name, Value.ToString()); string Result = string.Format("({0} {1} {2})", Name, Operator, CompName); return Result; } private string ResolveLinqToObject(Expression expression, object value, ExpressionType? expressiontype = null) { var MethodCall = expression as MethodCallExpression; var MethodName = MethodCall.Method.Name; switch (MethodName)//这里其实还可以改成反射调用,不用写switch { case "Contains": if (MethodCall.Object != null) return Like(MethodCall); return In(MethodCall, value); case "Count": return Len(MethodCall, value, expressiontype.Value); case "LongCount": return Len(MethodCall, value, expressiontype.Value); default: throw new Exception(string.Format("不支持{0}方法的查找!", MethodName)); } } private string SetArgument(string name, string value) { name = "@" + name; string temp = name; while (Argument.ContainsKey(temp)) { int code = Guid.NewGuid().GetHashCode(); if (code < 0) code *= -1; temp = name + code; } Argument[temp] = value; return temp; } private string In(MethodCallExpression expression, object isTrue) { var Argument1 = (expression.Arguments[0] as MemberExpression).Expression as ConstantExpression; var Argument2 = expression.Arguments[1] as MemberExpression; var Field_Array = Argument1.Value.GetType().GetFields().First(); object[] Array = Field_Array.GetValue(Argument1.Value) as object[]; List
SetInPara = new List
(); for (int i = 0; i < Array.Length; i++) { string Name_para = "InParameter" + i; string Value = Array[i].ToString(); string Key = SetArgument(Name_para, Value); SetInPara.Add(Key); } string Name = Argument2.Member.Name; string Operator = Convert.ToBoolean(isTrue) ? "in" : " not in"; string CompName = string.Join(",", SetInPara); string Result = string.Format("{0} {1} ({2})", Name, Operator, CompName); return Result; } private string Like(MethodCallExpression expression) { object Temp_Vale = (expression.Arguments[0] as ConstantExpression).Value; string Value = string.Format("%{0}%", Temp_Vale); string Name = (expression.Object as MemberExpression).Member.Name; string CompName = SetArgument(Name, Value); string Result = string.Format("{0} like {1}", Name, CompName); return Result; } private string Len(MethodCallExpression expression, object value, ExpressionType expressiontype) { object Name = (expression.Arguments[0] as MemberExpression).Member.Name; string Operator = GetOperator(expressiontype); string CompName = SetArgument(Name.ToString(), value.ToString()); string Result = string.Format("len({0}){1}{2}", Name, Operator, CompName); return Result; } }

 

 

 

static void Main(string[] args)        {            string[] Names = { "Andy", "Amy", "Mike" };            Expression
> func = x => (!Names.Contains(x.Name) && (x.Name == "A" || x.Name.Count() > 5)); ResolveExpress resolve = new ResolveExpress(); resolve.ResolveExpression(func); Console.WriteLine(resolve.SqlWhere); foreach (var item in resolve.Paras) { Console.WriteLine(item.ParameterName + ":" + item.Value); } Console.ReadKey(); }

结果:

 

这里有几个重要的东西要给大家讲下

string[] Names={"Andy","Amy","Mike"};

1.)x => Names.Contains(x.Name);

2.)x => Names.Contains(x.Name)==false;

3.)x => !Names.Contains(x.Name);

这3种在Expression中的表现都不一样

1的话会看成是一个静态方法(MethodCallExpression)

2的话会看成是一个2元运算(BinaryExpression)

3的话会看成是一个1元运算(UnaryExpression)

所以我们都要支持,处理都有所不同。

还有

x=>x.Birthday<DateTime.Now;

string name="123";

x=>x.Name==name;

x=>x.Name=="123"

的处理也不一样。大家可以在例子中细细的看看。

 

这样的构造使得我们切换数据库变得非常简单。因为我们程序中的查询都是基于lambda。换了数据库只要添加一个对应的lamdba转数据库查询条件的实现就可以了。写得够多了。至于数据层怎么封装,到了这一步它已经变得没什么难度了。希望大家能从文章中有所启发和帮助

 

 

补充点东西

IEnumerable和IQueryable有什么不同?

为什么EF查询后返回的是IQueryable<T>而不是IEnumerable<T>。我们对着IQueryable<T>F12去看看。

 

啥都没,就继承了几个接口。鼠标移到IQueryable上。F12

 

IQueryable中有3个属性。

Type是类型。

Expresstion是表达式。

那IQueryProvider是什么?

 

再看看IQueryProvider接口的定义。

CreateQuery是创建查询条件

Execute是执行查询(通常在GetEnumerator()中调用)

 

当我们IQueryable<T>.Where(x=>x.xxx=="123")时。其实Where方法内部应该是调用了IQueryable接口中的IQueryProvider属性的CreateQuery(Expresstion expresstion)方法,然后将方法的返回值又返回出来。

而参数(Expresstion )呢?则是IQueryable<T>.Where(x=>x.xxx=="123")2部分的Expresstion相并。所以IQueryable只是创建条件。所以51楼的朋友说得非常对。

那什么时候执行呢?因为我们的IQueryable<T>继承了IEnumabler<T>,所以我们必须实现GetEnumerator()。我们ToList或foreach时,其实就会调用GetEnumerator()。这时我们就调用Execute进行解析Expresstion,从而得到我们想要的结果。

总结就是IQueryable只是创建条件,当我们调用a.Where(x=>xxx)时,其实是将a与后面的条件相并,生成一个新的IQueryable。当我们foreach时就会调用GetEnumerator()。这时我们一般会调用IQueryProvider里的Execute去解析Expresstion并查询出我们想要的结果

 

我也知道这篇文章介绍的和我们所说的“ORM”相差很远,但是所谓的ORM最复杂的莫非查询部分了,而依照我这思路走下去,我觉得是可以自己完成一个的。。

我刚开始写博客第二天,没想到这文章反响这么大。我承认有点重复造轮子,也非常不成熟,但我还是想通过自己的思考去构造属于自己的东西。

不知道大家有没看过头文字D,里头有个组织叫东堂垫,他们里面的人是拆掉ABS的。因为他们会长说,你要先学会不使用ABS进行刹车才知道ABS的真谛

转载于:https://www.cnblogs.com/yangmingyu/p/6904118.html

转载地址:https://blog.csdn.net/weixin_30843605/article/details/95264774 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:SpringCloud 配置文件 application.yml和 bootstrap.yml区别
下一篇:OSX下git diff/merge 可视化工具 P4Merge 环境配置步骤

发表评论

最新留言

留言是一种美德,欢迎回访!
[***.207.175.100]2024年03月07日 20时59分47秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

用c语言编写小于n的所有素数,关于求N以内素数的一点小问题(N小于一亿) 2019-04-21
华为100万部鸿蒙,2019年Q4发布 华为100万部鸿蒙OS手机已开测 2019-04-21
android+大富翁+局域网,【图片】大富翁6局域网(LAN)多人联机教程(求精)_大富翁吧_百度贴吧... 2019-04-21
rn webview加载本地静态html,React Native - Webview 加载本地文件 2019-04-21
vscode 不能使用中文输入法_vscode中vim插件设置 2019-04-21
fi sap 凭证冲销 稅_SAP中的成本要素 2019-04-21
kangle主机怎么配置MySQL_kangle web服务+easypanel主机控制面板快速搭建网站和数据库以及管理空间详细教程... 2019-04-21
mysql 翻页 存储过程_MySQl通用翻页(存储过程) 2019-04-21
2020word替换所有文本_Excel字符函数(5):REPLACE、SUBSTITUTE查找替换函数之区别... 2019-04-21
win10安装ipython_win10环境 ipython app.py 8080 这里为什么是ipython 这步无法启动 2019-04-21
mysql多字段存储过程_mysql 的存储过程_多字段 2019-04-21
python怎么创建字符串列表_如何在python列表中为每个字符串创建子列表? 2019-04-21
vba ado 执行多条mysql 语句_access 按钮 多条sql语句 VBA 2019-04-21
弹性方法计算连续梁板内力_(梁板结构)混凝土结构设计复习题及答案 2019-04-21
java root权限_android java获得root权限调用linux命令 | 学步园 2019-04-21
java最小化窗体_JAVA窗体最大化最小化控制+托盘 2019-04-21
java 注解 数组默认值_Java注解默认值 2019-04-21
java流程语句_Java流程控制语句 2019-04-21
java require_java正则中的requireEnd和hitEnd 2019-04-21
400错误java_java代码转化成c#代码 报400错误 2019-04-21