上篇文章標題亂起,被吐槽了,這次學乖了。
上篇文章中介紹了如何解析Expression生成對應的SQL語句,以及IQueryable的一些概念,以及我們所搭建的框架的思想等。但還沒把它們結合並應用起來。這一篇文章將更黃更暴力,揭露IQueryable在實際使用中延遲加載的實現原理,結合上篇對Expression的解析,我們來實現一個自己的“延遲加載”
如果還不太了解如何解析Expression和IQueryable的一些基本概念,可以先看看我的上篇文章
我們先來做些基本工作,定義一個IDataBase接口,里面可以定義些查詢,刪除,修改,新增等方法,為了節約時間,我們就定義一個查詢和刪除的方法,再定義一個獲取IQueryable<T>實例的方法
public interface IDataBase { List<T> FindAs<T>(Expression<Func<T, bool>> lambdawhere); int Remove<T>(Expression<Func<T, bool>> lambdawhere); IQueryable<T> Source<T>(); }
再添加一個類DBSql,實現我們上面的IDataBase接口,這個類是負責提供對sql數據庫的操作
public class DBSql : IDataBase { public List<T> FindAs<T>(Expression<Func<T, bool>> lambdawhere) { throw new NotImplementedException(); } public int Remove<T>(Expression<Func<T, bool>> lambdawhere) { throw new NotImplementedException(); } public IQueryable<T> Source<T>() { throw new NotImplementedException(); } }
IQueryable<T>
上篇文章中有個朋友的回復對IQueryable的解釋十分到位,“IQueryable只存貯條件,不立即運行,從而可以實現延遲加載。”那它是如何存貯條件,如何延遲加載的?
這時我們為了提供 public IQueryable<T> Source<T>() 所需的對象。我們再來建一個SqlQuery類,實現IQueryable<T>。
public class SqlQuery<T> : IQueryable<T> { public IEnumerator<T> GetEnumerator() { throw new NotImplementedException(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw new NotImplementedException(); } public Type ElementType { get { throw new NotImplementedException(); } } public Expression Expression { get { throw new NotImplementedException(); } } public IQueryProvider Provider { get { throw new NotImplementedException(); } } }
看到這里大家都不陌生吧?
GetEnumerator()是IEnumerable<T>里的。有了它我們就能foreach了。有泛型和非泛型版本,所以有2個
Type提供訪問當前對象的類型(反正由你定義。。。)
Expression是貯存查詢條件的
IQueryProvider簡單的翻譯過來就是查詢提供者,它是負責創建查詢條件和執行查詢的。我們寫一個SqlProvider類來實現它
public class SqlProvider<T> : IQueryProvider { public IQueryable<TElement> CreateQuery<TElement>(Expression expression) { throw new NotImplementedException(); } public IQueryable CreateQuery(Expression expression) { throw new NotImplementedException(); } public TResult Execute<TResult>(Expression expression) { throw new NotImplementedException(); } public object Execute(Expression expression) { throw new NotImplementedException(); } }
CreateQuery是創建查詢條件。。
平時我們
IQueryable query=xxx源;
query=query.Where(x=>x.Name=="123");
這時Where方法里做的其實就是將前面query的Expression屬性和Where里的(x=>x.Name=="123")相並,並且調用Provider屬性里的CreateQuery方法。我們可以把我們的代碼改成這樣,來看看到底是不是這么回事。
public class DBSql : IDataBase { public IQueryable<T> Source<T>() { return new SqlQuery<T>(); } public List<T> FindAs<T>(Expression<Func<T, bool>> lambdawhere) { throw new NotImplementedException(); } public int Remove<T>(Expression<Func<T, bool>> lambdawhere) { throw new NotImplementedException(); } } public class SqlQuery<T> : IQueryable<T> { private Expression _expression; private IQueryProvider _provider; public SqlQuery() { _provider = new SqlProvider<T>(); _expression = Expression.Constant(this); } public SqlQuery(Expression expression, IQueryProvider provider) { _expression = expression; _provider = provider; } public IEnumerator<T> GetEnumerator() { throw new NotImplementedException(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw new NotImplementedException(); } public Type ElementType { get { return typeof(SqlQuery<T>); } } public Expression Expression { get { return _expression; } } public IQueryProvider Provider { get { return _provider; } } } public class SqlProvider<T> : IQueryProvider { public IQueryable<TElement> CreateQuery<TElement>(Expression expression) { IQueryable<TElement> query = new SqlQuery<TElement>(expression, this); return query; } public IQueryable CreateQuery(Expression expression) { throw new NotImplementedException(); } public TResult Execute<TResult>(Expression expression) { throw new NotImplementedException(); } public object Execute(Expression expression) { throw new NotImplementedException(); } }
public class Staff { public int ID { get; set; } public string Code { get; set; } public string Name { get; set; } public DateTime? Birthday { get; set; } public bool Deletion { get; set; } } static void Main(string[] args) { IDataBase db = new DBSql(); IQueryable<Staff> query = db.Source<Staff>(); string name = "張三"; Expression express = null; query = query.Where(x => x.Name == "趙建華"); express = query.Expression; query = query.Where(x => x.Name == name); express = query.Expression; }
段點打在
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
每次query.Where都會跑這里來。並且Expression都是前后相並的結果。


到了這一步,相信大家都明白了IQueryable只存貯條件這個概念了吧。
那延遲加載呢?什么時候加載啊!當我們foreach或者ToList/ToArray時啊。這時你想到了什么?GetEnumerator()。在調用GetEnumerator()時。我們再調用Provider里的Execute(Expression)。里面解析Expression,生成SQL語句,通過反射的方式生成實例,再一個個返回回去。完成!下面我直接給代碼了。解析Expression的類我也改了,這個更黃更暴力。
public class ResolveExpression { public Dictionary<string, object> Argument; public string SqlWhere; public SqlParameter[] Paras; private int index = 0; /// <summary> /// 解析lamdba,生成Sql查詢條件 /// </summary> /// <param name="expression"></param> /// <returns></returns> public void ResolveToSql(Expression expression) { this.index = 0; this.Argument = new Dictionary<string, object>(); this.SqlWhere = Resolve(expression); this.Paras = Argument.Select(x => new SqlParameter(x.Key, x.Value)).ToArray(); } private object GetValue(Expression expression) { if (expression is ConstantExpression) return (expression as ConstantExpression).Value; if (expression is UnaryExpression) { UnaryExpression unary = expression as UnaryExpression; LambdaExpression lambda = Expression.Lambda(unary.Operand); Delegate fn = lambda.Compile(); return fn.DynamicInvoke(null); } if (expression is MemberExpression) { MemberExpression member = expression as MemberExpression; string name = member.Member.Name; var constant = member.Expression as ConstantExpression; if (constant == null) throw new Exception("取值時發生異常" + member); return constant.Value.GetType().GetFields().First(x => x.Name == name).GetValue(constant.Value); } throw new Exception("無法獲取值" + expression); } 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) { object value = GetValue(binary.Right); return ResolveFunc(binary.Left, value, binary.NodeType); } if (binary.Left is MethodCallExpression && (binary.Right is UnaryExpression || binary.Right is MemberExpression)) { object value = GetValue(binary.Right); return ResolveLinqToObject(binary.Left, value, binary.NodeType); } } if (expression is UnaryExpression)//解析一元運算符 { UnaryExpression unary = expression as UnaryExpression; if (unary.Operand is MethodCallExpression) { return ResolveLinqToObject(unary.Operand, false); } if (unary.Operand is MemberExpression) { return ResolveFunc(unary.Operand, false, ExpressionType.Equal); } } if (expression is MethodCallExpression)//解析擴展方法 { return ResolveLinqToObject(expression, true); } if (expression is MemberExpression)//解析屬性。。如x.Deletion { return ResolveFunc(expression, true, ExpressionType.Equal); } 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, object value, ExpressionType expressiontype) { string Name = (left as MemberExpression).Member.Name; string Operator = GetOperator(expressiontype); string Value = value.ToString(); string CompName = SetArgument(Name, Value); 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)) { temp = name + index; index = index + 1; } Argument[temp] = value; return temp; } private string In(MethodCallExpression expression, object isTrue) { var Argument1 = expression.Arguments[0]; var Argument2 = expression.Arguments[1] as MemberExpression; var fieldValue = GetValue(Argument1); object[] array = fieldValue 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) { Expression argument = expression.Arguments[0]; object Temp_Vale = GetValue(argument); 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; } }
public interface IDataBase { List<T> FindAs<T>(Expression<Func<T, bool>> lambdawhere); int Remove<T>(Expression<Func<T, bool>> lambdawhere); IQueryable<T> Source<T>(); }
namespace Data.DataBase { public class DBSql : IDataBase { private readonly static string ConnectionString = @"Data Source=.;Initial Catalog=btmmcms-Standard;Persist Security Info=True;User ID=sa;Password=sa;"; public IQueryable<T> Source<T>() { return new SqlQuery<T>(); } public List<T> FindAs<T>(Expression<Func<T, bool>> lambdawhere) { using (SqlConnection Conn = new SqlConnection(ConnectionString)) { using (SqlCommand Command = new SqlCommand()) { try { Command.Connection = Conn; Conn.Open(); string sql = string.Format("select * from {0}", typeof(T).Name); if (lambdawhere != null) { ResolveExpression resolve = new ResolveExpression(); resolve.ResolveToSql(lambdawhere); sql = string.Format("{0} where {1}", sql, resolve.SqlWhere); Command.Parameters.AddRange(resolve.Paras); } //為了測試,就在這里打印出sql語句了 Console.WriteLine(sql); Command.CommandText = sql; SqlDataReader dataReader = Command.ExecuteReader(); List<T> ListEntity = new List<T>(); while (dataReader.Read()) { var constructor = typeof(T).GetConstructor(new Type[] { }); T Entity = (T)constructor.Invoke(null); foreach (var item in Entity.GetType().GetProperties()) { var value = dataReader[item.Name]; if (value == null) continue; if (value is DBNull) value = null; item.SetValue(Entity, value, null); } ListEntity.Add(Entity); } if (ListEntity.Count == 0) return null; return ListEntity; } catch (Exception ex) { throw ex; } finally { Conn.Close(); } } } } public int Remove<T>(Expression<Func<T, bool>> lambdawhere) { throw new NotImplementedException(); } } public class SqlQuery<T> : IQueryable<T> { private Expression _expression; private IQueryProvider _provider; public SqlQuery() { _provider = new SqlProvider<T>(); _expression = Expression.Constant(this); } public SqlQuery(Expression expression, IQueryProvider provider) { _expression = expression; _provider = provider; } public IEnumerator<T> GetEnumerator() { var result = _provider.Execute<List<T>>(_expression); if (result == null) yield break; foreach (var item in result) { yield return item; } } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw new NotImplementedException(); } public Type ElementType { get { return typeof(SqlQuery<T>); } } public Expression Expression { get { return _expression; } } public IQueryProvider Provider { get { return _provider; } } } public class SqlProvider<T> : IQueryProvider { public IQueryable<TElement> CreateQuery<TElement>(Expression expression) { IQueryable<TElement> query = new SqlQuery<TElement>(expression, this); return query; } public IQueryable CreateQuery(Expression expression) { throw new NotImplementedException(); } public TResult Execute<TResult>(Expression expression) { MethodCallExpression methodCall = expression as MethodCallExpression; Expression<Func<T, bool>> result = null; while (methodCall != null) { Expression method = methodCall.Arguments[0]; Expression lambda = methodCall.Arguments[1]; LambdaExpression right = (lambda as UnaryExpression).Operand as LambdaExpression; if (result == null) { result = Expression.Lambda<Func<T, bool>>(right.Body, right.Parameters); } else { Expression left = (result as LambdaExpression).Body; Expression temp = Expression.And(right.Body, left); result = Expression.Lambda<Func<T, bool>>(temp, result.Parameters); } methodCall = method as MethodCallExpression; } var source = new DBSql().FindAs<T>(result); dynamic _temp = source; TResult t = (TResult)_temp; return t; } public object Execute(Expression expression) { throw new NotImplementedException(); } } }
搞定,這時可以改下數據庫連接,連到自己的數據庫,然后像下面這樣,添加一個實體類(要與數據庫表對應),就可以使用了
class Program { public class Staff { public int ID { get; set; } public string Code { get; set; } public string Name { get; set; } public DateTime? Birthday { get; set; } public bool Deletion { get; set; } } static void Main(string[] args) { IDataBase db = new DBSql(); IQueryable<Staff> query = db.Source<Staff>(); query = query.Where(x => x.Name == "張三"); foreach (var item in query) { } } }
是不是很簡單?
雖然信息量有點大,但慢慢理清並消化,我相信會對你又很大幫助!
