拋棄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.Code =="張三" && 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的類。。下面我就直接給出我自己寫的實現代碼了。。

 

 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);
                }
            }
            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;
        }

        /// <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轉數據庫查詢條件的實現就可以了。寫得夠多了。至於數據層怎么封裝,到了這一步它已經變得沒什么難度了。希望大家能從文章中有所啟發和幫助

 

下篇文章將結合解析Expression和IQueryable<T>來實現延遲加載

 

補充點東西

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的真諦


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM