LinqToDB 源码分析——轻谈Linq查询
LinqToDB框架最大的优势应该是实现了对Linq的支持。如果少了这一个功能相信他在使用上的快感会少了一个层次。本来笔者想要直接讲解LinqToDB框架是如何实现对Linq的支持。写到一半的时候却发现本系列在内容上的引导显得格外的生硬。思考在三最后还是决定在讲解LinqToDB框架之前来一章过度文。
Linq查询的原理
我们在学习Linq的时候会见到一些很常见的关键词语。比如Linq To SQL、Linq To Objects、Linq To XML等。事实这些一般都是根据不同的数据源来进行命名的。 说实话笔者当初学习的时候,看到这些命名险些以为只有这几种。事实不是这样子的。Linq有俩个核心类——Enumerable类和Queryable类。这俩个类可以说贯穿整个Linq知识体系。如果有心的朋友可以点开对应的dll包就是发现他们都在System.Linq命名空间下。同时他们都是用于扩展相应的静态方法。而且方法名大至相同。然后他们却在本质上有着细微的差别。Enumerable类是对IEnumerable<T>接口进行扩展并且传入了Func类型的参数。数据源是来自于内存中的。而Queryable类是对IQueryable<T>接口进行扩展,传入参数是表达式(Expression类型)。数据源是来自于第三方。比如SQL Server、MySql等。
Linq的思想就是提供一个统一模型操作来处理数据。所以本质来讲对数据源不是很讲究。比如数据源是文件,或则说数据源是Excel之类的。相信可能有人已经看到过Linq To Excel呢?主要辛苦还是这些开发底层的人。对于使用者来讲没有什么多大的差别。Linq现在面对数据源而扩展功能有很多。其中专对数据库来讲,最流行还是有Linq To SQL。而且扩展数据库的Linq功能大多数都用IQueryable<T>接口。当然,这不是说用IEnumerable<T>接口就不行了。只是这俩种接口在实现上有着很大的差别。IEnumerable<T>接口我们都知道他一般是专对于内存的。这意味着我们必须把相应的数据全部加载到内存中才可以进行查询。这样子的操作太伤性能了。而IQueryable<T>接口我们可以巧妙的用上表达式树(Expression Tree)进行转化生成对应的数据库SQL语句,然后在执行数据库。这才是显得合理。
ORM思想能流行大体上可以说是因为他的思想更加贴切于人类的思维方式。在笔者看来如果把Linq技术说成也是ORM思想的产物之一,这样子的说法也不为过。这也是笔者喜欢Linq的地方。LinqToDB框架只所以都能支持Linq。不可否认也是依据这一种上面所讲的原理来实现的。大体的想法如下。
- 实现Linq提供的IQueryable<T>接口和IQueryProvider接口。生成相关的表达式树。
- 把对应的表达式树转化生成对应数据库的SQL语句。并执行。
- 根据映射的信息,生成对应的集合类。(这里的集合类是指SQL语句执行结果转成类放入的集合)
实现自定义的Linq查询一定离不开俩类——IQueryable<T>接口和IQueryProvider接口。上面的工作可以说都在这俩类上面。IQueryable<T>接口一般用于生成对应的表达式树。而IQueryProvider接口用于执行表达式树,转化成对应的SQL语句,执行数据库并生成映射的模型对象。
注意:IQueryable<T>接口是什么样子生成相关的表达式树呢?让笔者来讲的话,笔者觉得有一点浪费时间。但是不要当心博客园里面有一位大神写的博文一定能满足你——王清培的《.NET深入解析LINQ框架》。
实现Linq查询
支持Linq查询本意上就是实现上面所讲的俩个接口。当你实现IQueryable<T>接口的时候,VS提示你实现三个属性。如果你F12进去查看他有些什么内容的话,你会发现什么也没有。为了方便笔者还是把他贴出来了。
IQueryable<T>接口:
public interface IQueryable<out T> : IEnumerable<T>, IQueryable, IEnumerable { }
IQueryable接口:
public interface IQueryable : IEnumerable { Type ElementType { get; } Expression Expression { get; } IQueryProvider Provider { get; } }
这些属性全部来自于IQueryable接口。同时你还会发现——我去!他即然继承了IEnumerable<T>接口。笔者不惊的感叹微软真会玩。ElementType属性的作用也正如他的名字一样子——元素类型。用于指定当前要查询的是什么类型的数据。对于Expression属性的话,也正如上面笔者讲到IQueryable<T>接口会为我们建立一个表达式树。Expression属性便是用于建立表达式树的。最后一个Provider属性。他是IQueryProvider类型的。用于执行表达式生成对应的数据库SQL语句和执行数据库。
IQueryProvider接口有四个方法。事实上应该说是俩个才对。因为他们是俩俩功能相同。IQueryable<T>接口生成完表达式树之后,最终执行的有俩个方法一个是来IEnumerable<T>的GetEnumerator方法,一个是来IQueryProvider接口的Execute方法。那么笔者上面讲到的“执行表达式生成对应的数据库SQL语句和执行数据库”的功能也是在这俩个方法中实现的。
好了。笔者说在多也没有什么用。不如笔者写一个小小的应用来说明这一切。如下
1 public class AomiQuery<T>:IOrderedQueryable<T> 2 { 3 public AomiQuery() 4 { 5 this.Expression = System.Linq.Expressions.Expression.Constant(this); 6 this.Provider = new AomiQueryProvider(); 7 } 8 public AomiQuery(Expression expression, IQueryProvider provider) 9 { 10 this.Expression = expression; 11 this.Provider = provider; 12 } 13 public IEnumerator<T> GetEnumerator() 14 { 15 return (Provider.Execute<IEnumerable<T>>(Expression)).GetEnumerator(); 16 } 17 18 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 19 { 20 return (Provider.Execute<System.Collections.IEnumerable>(Expression)).GetEnumerator(); 21 } 22 23 public Type ElementType 24 { 25 get { return typeof(T); } 26 } 27 28 public Expression Expression { private set; get; } 29 30 public IQueryProvider Provider { private set; get; } 31 }
上面这段代码中,笔者用是不是IQueryable<T>接口而是IOrderedQueryable<T>接口。事实上不有多大的差别。IOrderedQueryable<T>接口是继承IQueryable<T>接口。官方的说法是IQueryable<T>接口不能实现Order by功能。IOrderedQueryable<T>接口却可以。当然关于这一点,有兴趣的读者们可以自行去看看。
IQueryable<T>接口实现完了。让我们在实现一下IQueryProvider接口吧。
1 public class AomiQueryProvider:IQueryProvider 2 { 3 4 public IQueryable<TElement> CreateQuery<TElement>(Expression expression) 5 { 6 return new AomiQuery<TElement>(expression, this); 7 } 8 9 public IQueryable CreateQuery(System.Linq.Expressions.Expression expression) 10 { 11 Type elementType = TypeSystem.GetElementType(expression.Type); 12 try 13 { 14 return (IQueryable)Activator.CreateInstance(typeof(AomiQuery<>).MakeGenericType(elementType), new object[] { this, expression }); 15 } 16 catch (System.Reflection.TargetInvocationException tie) 17 { 18 throw tie.InnerException; 19 } 20 } 21 22 public TResult Execute<TResult>(Expression expression) 23 { 24 bool IsEnumerable = (typeof(TResult).Name == "IEnumerable`1"); 25 return (TResult)this.ExecuteReader(expression,IsEnumerable); 26 } 27 28 public object Execute(Expression expression) 29 { 30 return this.ExecuteReader(expression); 31 } 32 33 public object ExecuteReader(Expression expression, bool isEnumerable = false) 34 { 35 if (expression is MethodCallExpression) 36 { 37 MethodCallExpression mce = expression as MethodCallExpression; 38 SqlConnection connection = new SqlConnection("Data Source=.;Initial Catalog=Northwind;Persist Security Info=True;User ID=sa;Password=123"); 39 SqlCommand command = new SqlCommand(); 40 command.Connection = connection; 41 42 StringBuilder commandText = new StringBuilder(); 43 44 if (mce != null && mce.Method.DeclaringType == typeof(Queryable) && mce.Method.Name == "Where") 45 { 46 commandText.Append("SELECT * FROM "); 47 48 ConstantExpression ce = mce.Arguments[0]as ConstantExpression; 49 IQueryable queryable = ce.Value as IQueryable; 50 51 commandText.Append(queryable.ElementType.Name); 52 commandText.Append(" WHERE "); 53 54 UnaryExpression ue = mce.Arguments[1] as UnaryExpression; 55 LambdaExpression lambda = ue.Operand as LambdaExpression; 56 BinaryExpression be = lambda.Body as BinaryExpression; 57 MemberExpression lme = be.Left as MemberExpression; 58 ConstantExpression rce = be.Right as ConstantExpression; 59 60 commandText.Append(lme.Member.Name); 61 62 switch (be.NodeType) 63 { 64 case ExpressionType.And: 65 commandText.Append(" AND "); 66 break; 67 case ExpressionType.Or: 68 commandText.Append(" OR "); 69 break; 70 case ExpressionType.Equal: 71 commandText.Append(" = "); 72 break; 73 case ExpressionType.NotEqual: 74 commandText.Append(" <> "); 75 break; 76 case ExpressionType.LessThan: 77 commandText.Append(" < "); 78 break; 79 case ExpressionType.LessThanOrEqual: 80 commandText.Append(" <= "); 81 break; 82 case ExpressionType.GreaterThan: 83 commandText.Append(" > "); 84 break; 85 case ExpressionType.GreaterThanOrEqual: 86 commandText.Append(" >= "); 87 break; 88 } 89 90 commandText.Append(rce.Value); 91 92 } 93 94 95 command.CommandText = commandText.ToString(); 96 97 List<Products> proList = new List<Products>(); 98 99 connection.Open(); 100 101 SqlDataReader dr = command.ExecuteReader(); 102 103 while (dr.Read()) 104 { 105 Products product = new Products(); 106 product.ProductID = Convert.ToInt32(dr["ProductID"]); 107 product.ProductName = Convert.ToString(dr["ProductName"]); 108 proList.Add(product); 109 } 110 111 dr.Close(); 112 connection.Close(); 113 114 return isEnumerable ? proList.AsEnumerable() : proList; 115 116 } 117 118 return null; 119 }
在笔者看来大量的工作都放在了IQueryProvider接口上面,IQueryable<T>接口显得更加像一个帮手——就是帮我们建表达式树。IQueryProvider接口里面有俩方法叫CreateQuery。从代码中我们就可以看到他会返回一个新的IQueryable<T>接口实例。由于笔者这边只是写一个where功能。如果有order by功能的话,那么CreateQuery方法会被在调用一次。同时传入where过程中生成的表达式。当然这个时候我们还要在新建一个IQueryable<T>接口实例。注意每一次新建IQueryable<T>接口实例都会把上一个的表达式传入到实例的Expression属性。具体的情况,我觉得读者们可以自己做一个小试验来看看会比较好。如果你看过王大神的博文的话,相信这个一定不在话下。上面红色的代码是笔者处理表达式树的。在正式开发过程中往往是不可能这样子做的。笔者这边只是想要表达一个意思。在执行数据库之前,一定要先转化成对应的SQL。
执行代码:
static void Main(string[] args) { AomiQuery<Products> aomiProducts = new AomiQuery<Products>(); var query = from p in aomiProducts where p.ProductID > 30 select p; List<Products> proList = query.ToList(); foreach (Products p in proList) { Console.WriteLine("ProductID:{0} ----------------> ProductName:{1}", p.ProductID, p.ProductName); } Console.ReadKey(); }
执行结果:
笔者小语
这一篇文章笔者写了俩遍。笔者第一次写的时候,把他写成Linq的教程去了。等结束的时候才明白我为什么要写Linq呢?我只要写跟LinqToDB相关的知识就可以了。最后删 了重来一遍。相当的无语。