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语句和执行数据库”的功能也是在这俩个方法中实现的。
好了。笔者说在多也没有什么用。不如笔者写一个小小的应用来说明这一切。如下
public class AomiQuery<T>:IOrderedQueryable<T>
{
public AomiQuery()
{
this.Expression = System.Linq.Expressions.Expression.Constant(this);
this.Provider = new AomiQueryProvider();
}
public AomiQuery(Expression expression, IQueryProvider provider)
{
this.Expression = expression;
this.Provider = provider;
}
public IEnumerator<T> GetEnumerator()
{
return (Provider.Execute<IEnumerable<T>>(Expression)).GetEnumerator();
} System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return (Provider.Execute<System.Collections.IEnumerable>(Expression)).GetEnumerator();
} public Type ElementType
{
get { return typeof(T); }
} public Expression Expression { private set; get; } public IQueryProvider Provider { private set; get; }
}
上面这段代码中,笔者用是不是IQueryable<T>接口而是IOrderedQueryable<T>接口。事实上不有多大的差别。IOrderedQueryable<T>接口是继承IQueryable<T>接口。官方的说法是IQueryable<T>接口不能实现Order by功能。IOrderedQueryable<T>接口却可以。当然关于这一点,有兴趣的读者们可以自行去看看。
IQueryable<T>接口实现完了。让我们在实现一下IQueryProvider接口吧。
public class AomiQueryProvider:IQueryProvider
{ public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new AomiQuery<TElement>(expression, this);
} public IQueryable CreateQuery(System.Linq.Expressions.Expression expression)
{
Type elementType = TypeSystem.GetElementType(expression.Type);
try
{
return (IQueryable)Activator.CreateInstance(typeof(AomiQuery<>).MakeGenericType(elementType), new object[] { this, expression });
}
catch (System.Reflection.TargetInvocationException tie)
{
throw tie.InnerException;
}
} public TResult Execute<TResult>(Expression expression)
{
bool IsEnumerable = (typeof(TResult).Name == "IEnumerable`1");
return (TResult)this.ExecuteReader(expression,IsEnumerable);
} public object Execute(Expression expression)
{
return this.ExecuteReader(expression);
} public object ExecuteReader(Expression expression, bool isEnumerable = false)
{
if (expression is MethodCallExpression)
{
MethodCallExpression mce = expression as MethodCallExpression;
SqlConnection connection = new SqlConnection("Data Source=.;Initial Catalog=Northwind;Persist Security Info=True;User ID=sa;Password=123");
SqlCommand command = new SqlCommand();
command.Connection = connection; StringBuilder commandText = new StringBuilder(); 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 } command.CommandText = commandText.ToString(); List<Products> proList = new List<Products>(); connection.Open(); SqlDataReader dr = command.ExecuteReader(); while (dr.Read())
{
Products product = new Products();
product.ProductID = Convert.ToInt32(dr["ProductID"]);
product.ProductName = Convert.ToString(dr["ProductName"]);
proList.Add(product);
} dr.Close();
connection.Close(); return isEnumerable ? proList.AsEnumerable() : proList; } return null;
}
在笔者看来大量的工作都放在了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 > 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相关的知识就可以了。最后删 了重来一遍。相当的无语。
LinqToDB 源码分析——轻谈Linq查询的更多相关文章
- LinqToDB 源码分析——生成表达式树
当我们知道了Linq查询要用到的数据库信息之后.接下就是生成对应的表达式树.在前面的章节里面笔者就已经介绍过.生成表达式树是事实离不开IQueryable<T>接口.而处理表达式树离不开I ...
- LinqToDB 源码分析——生成与执行SQL语句
生成SQL语句的功能可以算是LinqToDB框架的最后一步.从上一章中我们可以知道处理完表达式树之后,相关生成SQL信息会被保存在一个叫SelectQuery类的实例.有了这个实例我们就可以生成对应的 ...
- LinqToDB 源码分析——前言
记得笔者进入公司的时候接触的第一个ORM框架是Entity Framework.为了Entity Framework也看了不些的英文资料(不是笔者装B哦).正式使用三个月后.笔者对他有一个全面性的认识 ...
- LinqToDB 源码分析——DataContext类
LinqToDB框架是一个轻量级的ORM框架.当然,功能上来讲一定比不上Entity Framework的强大.但是在使用上总让笔者感觉有一点Entity Framework的影子.笔者想过可能的原因 ...
- LinqToDB 源码分析——设计原理
我们知道实现了IQueryable<T>接口和IQueryProvider接口就可以使用Linq To SQL的功能.关于如何去实现的话,上一章也为我们引导了一个方向.LinqToDB框架 ...
- jQuery 2.0.3 源码分析Sizzle引擎 - 高效查询
为什么Sizzle很高效? 首先,从处理流程上理解,它总是先使用最高效的原生方法来做处理 HTML文档一共有这么四个API: getElementById 上下文只能是HTML文档 浏览器支持情况:I ...
- LinqToDB 源码分析——处理表达式树
处理表达式树可以说是所有要实现Linq To SQL的重点,同时他也是难点.笔者看完作者在LinqToDB框架里面对于这一部分的设计之后,心里有一点不知所然.由于很多代码没有文字注解.所以笔者只能接合 ...
- Linq转换操作之ToArray,ToList,ToDictionary源码分析
Linq转换操作之ToArray,ToList,ToDictionary源码分析 一:linq中的转换运算符 1. ToArray 我们经常用在linq查询上吧. linq只能运用在IEnumerab ...
- Linq特取操作之ElementAt,Single,Last,First源码分析
Linq特取操作之ElementAt,Single,Last,First源码分析 一:linq的特取操作 First/FirstOrDefault, Last/LastOrDefault, Eleme ...
随机推荐
- kafka配置与使用实例
kafka作为消息队列,在与netty.多线程配合使用时,可以达到高效的消息队列
- 如何利用tcpdump对mysql进行抓包操作
命令如下: tcpdump -s -l -w - dst -i eno16777736 |strings 其中-i指定监听的网络接口,在RHEL 7下,网络接口名不再是之前的eth0,而是 eno16 ...
- 【Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之集群概念介绍(一)
集群概念介绍(一)) 白宁超 2015年7月16日 概述:写下本文档的初衷和动力,来源于上篇的<oracle基本操作手册>.oracle基本操作手册是作者研一假期对oracle基础知识学习 ...
- (翻译)FIFO In Hardware
翻译一些自己觉得有价值的材料,工作中碰到英语大多数是读,基本没有写或者翻的,翻得不好不到位的敬请指摘. 同时也附原文以供参考. http://electronics.stackexchange.com ...
- [Android]使用Dagger 2来构建UserScope(翻译)
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/6237731.html 使用Dagger 2来构建UserSco ...
- 浅谈iptables 入站 出站以及NAT实例
--------------本文是自己工作上的笔记总结,适合的可以直接拿去用,不适合的,适当修改即可!--------------- iptbales默认ACCEPT策略,也称通策略,这种情况下可以做 ...
- windows charles response 乱码解决办法
使用windows 版本的charles来做代理,发现服务端返回的response会出现中文乱码的情况, 查看软件设置,遗憾的是并没有关于编码的选项. 好在charles windows版本安装目录下 ...
- EntityFramework 6 + Mysql 生成POCOs
问题 使用EDMX文件 EF Power Tools参数不正确的解决方法 对于"异常来自 HRESULT:0x80070057 (E_INVALIDARG)",有方法说" ...
- 我的MYSQL学习心得(四) 数据类型
我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(五) 运 ...
- 在CentOS上构建.net自动化编译环境
我们知道在Windows上我们很容易构建于MSBuild的自动化编译环境,那么在CentOS也是可以的,主要是需要Mono. 在这儿我们选择Jenkins+Gitlab+Mono在C ...