抛弃EF,20分构建一个属于自己的ORM框架

相信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<Staff>(x => x.Code == "张三" && x.Name.Contains("张"));
        }

        public static List<T> FindAs<T>(Expression<Func<T, bool>> 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.Age <50 && x.Name.Contains("张")‘是不是找到点灵感了?我们再继续把左边和右边拆开看看。

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

 class Program
    {
        static void Main(string[] args)
        {
            FindAs<Staff>(x => x.Code == "张三" && x.Name.Contains("张"));
            FindAs<Staff>(x => x.Age <= 12 && x.Name.Contains("张"));
            Console.ReadKey();
        }

        public static List<T> FindAs<T>(Expression<Func<T, bool>> 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的类。。下面我就直接给出我自己写的实现代码了。。

 /// <summary>
    /// 解析lamdba
    /// </summary>
    public class ResolveExpress
    {
        public Dictionary<string, object> Argument;
        public string SqlWhere;
        public SqlParameter[] Paras;

        /// <summary>
        /// 解析lamdba,生成Sql查询条件
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public void ResolveExpression(Expression expression)
        {
            this.Argument = new Dictionary<string, object>();
            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);
                }
                expression = unary.Operand;
            }
            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;
        }

        /// <summary>
        /// 根据条件生成对应的sql查询操作符
        /// </summary>
        /// <param name="expressiontype"></param>
        /// <returns></returns>
        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<string> SetInPara = new List<string>();
            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<Staff, bool>> 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转数据库查询条件的实现就可以了。写得够多了。至于数据层怎么封装,到了这一步它已经变得没什么难度了。希望大家能从文章中有所启发和帮助

时间: 2024-08-01 08:28:35

抛弃EF,20分构建一个属于自己的ORM框架的相关文章

Gora是一个类似Hibernate的ORM框架

Gora是一个类似Hibernate的ORM框架,但是不只是支持关系数据库,更重要支持NoSQL之类大数据的存储. 支持NoSQL之类大数据的存储 Apache Gora是一个开源的ORM(Object/Relation Mapping,对象关系映射)框架,主要为大数据提供内存数据模型与数据的持久化.目前Gora支持对于列数据.key-value数据,文档数据与RDBMS数据的存储,还支持使用Apache Hadoop来对对大数据进行分析 虽然目前市面上有很多不错的关系数据库的ORM框架,但是基

从零构建一个简单的 Python Web框架

为什么你想要自己构建一个 web 框架呢?我想,原因有以下几点: 你有一个新奇的想法,觉得将会取代其他的框架 你想要获得一些名气 你遇到的问题很独特,以至于现有的框架不太合适 你对 web 框架是如何工作的很感兴趣,因为你想要成为一位更好的 web 开发者. 接下来的笔墨将着重于最后一点.这篇文章旨在通过对设计和实现过程一步一步的阐述告诉读者,我在完成一个小型的服务器和框架之后学到了什么.你可以在这个代码仓库中找到这个项目的完整代码. 我希望这篇文章可以鼓励更多的人来尝试,因为这确实很有趣.它让

Apache thrift - 使用,内部实现及构建一个可扩展的RPC框架

本文首先介绍了什么是Apache Thrift,接着介绍了Thrift的安装部署及如何利用Thrift来实现一个简单的RPC应用,并简单的探究了一下Thrift的内部实现原理,最后给出一个基于Thrift的可扩展的分布式RPC调用框架,在中小型项目中是一个常见的SOA实践. Thrift介绍 Apache Thrift是Facebook 开发的远程服务调用框架,它采用接口描述语言(IDL)定义并创建服务,支持可扩展的跨语言服务开发,所包含的代码生成引擎可以在多种语言中,如 C++, Java,

《Entity Framework 6 Recipes》中文翻译系列 (20) -----第四章 ASP.NET MVC中使用实体框架之在MVC中构建一个CRUD示例

翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 第四章  ASP.NET MVC中使用实体框架 ASP.NET是一个免费的Web框架,它支持3种不同的技术来创建websites(网站)和Web应用:他们分别是,Web Pages,Web Forms,和MVC.虽然MVC是一种非常流行的,有完整的用于软件开发模式理论的技术,但它在ASP.NET中却是一种新的技术. 目前最新的版本是2012年发布的ASP.NET MVC4.自从2008年发布

C#大型电商项目优化(二)——嫌弃EF与抛弃EF

上一篇博文中讲述了使用EF开发电商项目的代码基础篇,提到EF后,一语激起千层浪.不少园友纷纷表示:EF不适合增长速度飞快的互联网项目,EF只适合企业级应用等等. 也有部分高手提到了分布式,确实,性能优化从数据库出发,初期就加索引,然后垂直拆分,水平拆分,读写分离,甚至是分布式事务,阳春白雪,格局很高.然而笔者希望通过渐进的过程来优化这个项目,我们缩小格局,从细节查看不同方案的优劣. 之前提过,使用EF最主要的原因是项目时间紧迫,EF搭建速度快,熟悉的同事也多,使用方便.这个决策确实帮助我们挺过了

从零开始构建一个的asp.net Core 项目

最近突发奇想,想从零开始构建一个Core的MVC项目,于是开始了构建过程. 首先我们添加一个空的CORE下的MVC项目,创建完成之后我们运行一下(Ctrl +F5).我们会在页面上看到"Hello World!". 既然是从零开始构建的项目,我们需要搞明白这个"Hello World!"是从哪里出现的? 点开我们的项目,我们会看到VS为我们生成了两个类,一个是Program.cs 和startup.cs,和一个空文件夹(wwwroot),除此之外VS在也没有为我们多

基于rsyslog+mysql+loganalyzer构建一个小而美的日志服务器

前言: 每当我们遇到问题抓耳挠腮,一脸懵那啥的时候,也许看一下日志就瞬间豁然开朗,所以,一个易用的日志服务器还是很重要的.我们先不介绍elk,那个牛逼吊炸天的日志分析系统.今天我们先来构建一个小的日志服务器.elk我们以后再说~ 正文: 当我们配置完一个服务,启动报错时,那感觉就像吃了啥一样,别提多难受,除非服务程序本身有检测配置文件的程序,不然我们就得依赖linux自己的集中日志服务器rsyslog了.不过,有时候也许我们想搜索一下某条日志,或者向用可视化的界面进行统计分析.此时我们可以把日志

我是如何构建一个持续发展的项目

说起项目,每个程序员都应该搭建过自己的项目,而我也搭建过数十个企业级或互联网级项目:在做企业级项目时也抽象了一套通过的开发脚手架ES方便开发,也做过一些通用的代码生成工具来生成通用项目架子或一些CRUD的代码.做这些平台或项目的时候或多或少给我一些启示和原则,而这些启示和原则一直指导着我内心方向,时刻指导我不偏离航线. 启示录 心中有原则 代码规范化 代码审查 代码重构 代码注释 代码逻辑抽象 工具类 项目闭环 持续改进 自动化 心中有原则 我认为这是搭建和维护项目的灵魂,失去了灵魂,项目虽然能

通过python 构建一个简单的聊天服务器

构建一个 Python 聊天服务器 一个简单的聊天服务器 现在您已经了解了 Python 中基本的网络 API:接下来可以在一个简单的应用程序中应用这些知识了.在本节中,将构建一个简单的聊天服务器.使用 Telnet,客户机可以连接到 Python 聊天服务器上,并在全球范围内相互进行通信.提交到聊天服务器的消息可以由其他人进行查看(以及一些管理信息,例如客户机加入或离开聊天服务器).这个模型如图 1 所示. 图 1. 聊天服务器使用 select 方法来支持任意多个客户机 聊天服务器的一个重要