LinqToDB 源码分析——生成表达式树
当我们知道了Linq查询要用到的数据库信息之后。接下就是生成对应的表达式树。在前面的章节里面笔者就已经介绍过。生成表达式树是事实离不开IQueryable<T>接口。而处理表达式树离不开IQueryProvider接口。LinqToDB框架跟这俩个接口有关系的有三个类:Table<T>类、ExpressionQuery<T>类、ExpressionQueryImpl<T>类。其中最重要的是ExpressionQuery<T>类。他是Table<T>和ExpressionQueryImpl<T>类的父类。而本章就是围绕这三个类进行的。
IQueryable<T>接口和IQueryProvider接口
ExpressionQuery<T>类是一个抽象类。他实现于IExpressionQuery接口。IExpressionQuery接口却同时继续了IQueryable<T>接口和IQueryProvider接口。所以ExpressionQuery<T>类实际上同时实现了俩个接口。而同时自己增加一个属性SqlText和一个跟IQueryable接口一样子的Expression属性。SqlText属性是用于获得当前表达式对应的SQL语句。这个属性显然在开发过程非常有用。而Expression属性是用于生成表达式树的。
public interface IExpressionQuery<out T> : IOrderedQueryable<T>, IQueryProvider
{
new Expression Expression { get; set; }
string SqlText { get; }
}
从前面的章节里面,我们可以知道实现IQueryable<T>接口和IQueryProvider接口的类有俩个部分的职责。一是帮助生成表达式树,二是用于Linq To SQL的数据源的入口。作者也相应的设计了俩个类。就是上面讲到的Table<T>和ExpressionQueryImpl<T>类。ExpressionQueryImpl<T>类用于前者,而Table<T>类用于后者。
var query = from p in dbContext.Products where p.ProductID == select p;
上面这段Linq查询中的dbContext.Products。事实上是通过IDataContext的静态扩展方法GetTable<T>来实现的。当然事情并没有这么简单。从源码中笔者看到这一个过程还离不开DataContextInfo类。DataContextInfo类是里面存放了关于DataContext类实例的信息。从代码量我们可以知道一个信息——DataContextInfo类必须有。
protected void Init(IDataContextInfo dataContextInfo, Expression expression)
{
DataContextInfo = dataContextInfo ?? new DefaultDataContextInfo();
Expression = expression ?? Expression.Constant(this);
}
我们都知道在生成表达式树的时候,实现俩个CreateQuery方法的重要性。LinqToDB框架也离不开这一点。CreateQuery<TElement>方法直接新建一个ExpressionQueryImpl<TElement>类。而CreateQuery方法用反射来实现(笔者认为这地方只有这里有看点其他的真的没有)。关于ExpressionQueryImpl<TElement>类真的没有什么可说的。笔者也就不提他了。
泛型CreateQuery方法:
IQueryable<TElement> IQueryProvider.CreateQuery<TElement>(Expression expression)
{
if (expression == null)
throw new ArgumentNullException("expressionreturn new ExpressionQueryImpl<TElement>(DataContextInfo, expression);
}
普通CreateQuery方法:
IQueryable IQueryProvider.CreateQuery(Expression expression)
{
if (expression == null)
throw new ArgumentNullException("expression"); var elementType = expression.Type.GetItemType() ?? expression.Type; try
{
return (IQueryable)Activator.CreateInstance(typeof(ExpressionQueryImpl<>).MakeGenericType(elementType), new object[] { DataContextInfo, expression });
}
catch (TargetInvocationException ex)
{
throw ex.InnerException;
}
}
从这里面笔者就是感觉出来IQueryable<T>接口更多只是帮助我们生成相应的表达式树。当然这里面离不开IQueryProvider接口的CreateQuery方法的帮忙。正因为CreateQuery方法我们可以知道每一个表达树节点都会新建一个对象。这个对象就是ExpressionQueryImpl<TElement>类。而这个对象里面的Expression属性就是当前节点的表达式。所以每新建一个对象都会把expression参数传入。
Query类
Linq查询的难点就是处理表达式。上面笔者粗略的讲了一些生成表达式。主要是因为前面章节中已经讲过这一部分的内容。而且LinqToDB框架关于这一点又没有做出什么特色的设计。但是关于处理表达式树的设计内容笔者感觉还有可以学习和介见的。所有处理表达树的类都在LinqToDB.Linq.Builder命名空间下。更是以XxxxBuilder的命名规则来新建相应的类。但是在讲处理表达树之前,笔者还是先讲一下关于Query类的内容。事实上从ExpressionQuery<T>的性属中我们就可以找到Query类的影子。
abstract class ExpressionQuery<T> : IExpressionQuery<T>
{
[NotNull]
public Expression Expression { get; set; }
[NotNull]
public IDataContextInfo DataContextInfo { get; set; } internal Query<T> Info;
internal object[] Parameters;
}
那么Query类到底是什么角色呢?又有什么作用呢?从《LinqToDB 源码分析——轻谈Linq查询》章节中我们能知道最后执行的方法有俩个Execute方法和GetEnumerator方法。所以Query类对应也有俩个方法跟他们相应。
ExpressionQuery类的Execute方法:
TResult IQueryProvider.Execute<TResult>(Expression expression)
{
return (TResult)GetQuery(expression, false).GetElement(null, DataContextInfo, expression, Parameters);
}
ExpressionQuery类的GetEnumerator方法:
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
IEnumerable<T> result = Execute(DataContextInfo, Expression);
IEnumerator<T> iEnumerator = result.GetEnumerator();
return iEnumerator;
}
GetEnumerator方法里面的Execute方法:
IEnumerable<T> Execute(IDataContextInfo dataContextInfo, Expression expression)
{
return GetQuery(expression, true).GetIEnumerable(null, dataContextInfo, expression, Parameters);
}
从源码中我们可以看到Query类的GetElement方法和GetIEnumerable方法各自对应一个最终执行方法。GetElement方法对应ExpressionQuery类的Execute方法,而GetIEnumerable方法对应ExpressionQuery类的GetEnumerator方法。不过,GetElement方法和GetIEnumerable方法都是Func类型。
class Query<T> : Query
{
//......
//......
//......
public Func<QueryContext, IDataContextInfo, Expression, object[], object> GetElement;
public Func<QueryContext, IDataContextInfo, Expression, object[], IEnumerable<T>> GetIEnumerable;
//......
//......
//...... }
从这里我们就能感觉到一点那就是好像最后执行会在Query类里面做。笔者只能说没有错。事实上关于Query类现在笔者也很难去形容他。这个类设计到底是一个什么样子的存在。相信也只有作者才能搞清楚。总之从代码上来看的话,他有一点像是保妈一样子。负责各个类之间的引导工作。
处理表达式树的工作并不是由Query类来做的。而由一个叫ExpressionBuilder类来做的(下一章会讲到)。但是Query类最后会负责接受ExpressionBuilder类产生的结果。Query类会拿着结果去找DataContext类来处理执行生成数据库结果。然后Query类在拿数据库结果去找MapInfo类生成对应的最终结果。看看。像不像保妈一样子。关于Query类的具体内容还是要接合ExpressionBuilder类才能介绍清楚。笔者后面会讲到。现在主要让我们看一下Query类是什么样子由来的。ExpressionQuery<T>类中有一个方法GetQuery方法。上面讲到的最后执行的方法(Execute方法和GetEnumerator方法)都会去调用他。Query类又调用自己本身的静态方法GetQuery。一切也就是从这里开始的。
ExpressionQuery<T>类的GetQuery方法:
Query<T> GetQuery(Expression expression, bool cache)
{
if (cache && Info != null)
return Info; var info = Query<T>.GetQuery(DataContextInfo, expression); if (cache)
Info = info; return info;
}
Query的静态方法GetQuery:
public static Query<T> GetQuery(IDataContextInfo dataContextInfo, Expression expr)
{
var query = FindQuery(dataContextInfo, expr); if (query == null)
{
lock (_sync)
{
query = FindQuery(dataContextInfo, expr); if (query == null)
{
if (Configuration.Linq.GenerateExpressionTest)
{
var testFile = new ExpressionTestGenerator().GenerateSource(expr);
DataConnection.WriteTraceLine(
"Expression test code generated: '" + testFile + "'.",
DataConnection.TraceSwitch.DisplayName);
} try
{
query = new ExpressionBuilder(new Query<T>(), dataContextInfo, expr, null).Build<T>();
}
catch (Exception)
{
if (!Configuration.Linq.GenerateExpressionTest)
{
DataConnection.WriteTraceLine(
"To generate test code to diagnose the problem set 'LinqToDB.Common.Configuration.Linq.GenerateExpressionTest = true'.",
DataConnection.TraceSwitch.DisplayName);
} throw;
} if (!query.DoNotChache)
{
query.Next = _first;
_first = query;
}
}
}
} return query;
}
我们可以明显的看到想要获得Query类,就必须通过ExpressionBuilder类来获得。这里的内容就多了。而除了这一点之外,作者也为Query类做了小缓存。ExpressionQuery<T>的性属Info是为了Query类本身的缓存。而FindQuery方法是为了所有的Query类缓存的。
1.Query类本身的缓存。如下,第一次用query变量的时候要加载实例化Query类。第二次在用query变量的时候就不必了。
static void Main(string[] args)
{
using (AdoContext dbContext = new AdoContext())
{
var query = from p in dbContext.Products where p.ProductID == select p;
List<Products> catalogsList = query.ToList();
List<Products> catalogsList1 = query.ToList();
}
}
2.所有的Query类缓存的。下面中query和query1、query3是一样子的。所以query1,query2是不会在加载实例Query类的。FindQuery方法用的缓存方式有一点像链接队列——只是笔者忘记了专业的名称。如果链接缓存中哪一个query被用最后会放到链接的最前面。如果在链接缓存中没有找到,就接在最后面。
static void Main(string[] args)
{
using (AdoContext dbContext = new AdoContext())
{
var query = from p in dbContext.Products where p.ProductID == select p;
var query1 = from p in dbContext.Products where p.ProductID == select p;
var query2 = from p in dbContext.Products where p.ProductName == "Aomi" select p;
var query3 = from p in dbContext.Products where p.ProductID == select p;
List<Products> catalogsList = query.ToList();
List<Products> catalogsList11 = query.ToList();
List<Products> catalogsList1 = query1.ToList();
List<Products> catalogsList2 = query2.ToList();
List<Products> catalogsList3 = query3.ToList();
}
}
LinqToDB框架是轻量级的ORM框架。所以笔者也很喜欢作者这样子设计缓存——简单而又实用。
好了。有于工作的原因。笔者本单只能介绍到这里了。
LinqToDB 源码分析——生成表达式树的更多相关文章
- LinqToDB 源码分析——处理表达式树
处理表达式树可以说是所有要实现Linq To SQL的重点,同时他也是难点.笔者看完作者在LinqToDB框架里面对于这一部分的设计之后,心里有一点不知所然.由于很多代码没有文字注解.所以笔者只能接合 ...
- LinqToDB 源码分析——生成与执行SQL语句
生成SQL语句的功能可以算是LinqToDB框架的最后一步.从上一章中我们可以知道处理完表达式树之后,相关生成SQL信息会被保存在一个叫SelectQuery类的实例.有了这个实例我们就可以生成对应的 ...
- 死磕以太坊源码分析之MPT树-下
死磕以太坊源码分析之MPT树-下 文章以及资料请查看:https://github.com/blockchainGuide/ 上篇主要介绍了以太坊中的MPT树的原理,这篇主要会对MPT树涉及的源码进行 ...
- 死磕以太坊源码分析之MPT树-上
死磕以太坊源码分析之MPT树-上 前缀树Trie 前缀树(又称字典树),通常来说,一个前缀树是用来存储字符串的.前缀树的每一个节点代表一个字符串(前缀).每一个节点会有多个子节点,通往不同子节点的路径 ...
- LinqToDB 源码分析——设计原理
我们知道实现了IQueryable<T>接口和IQueryProvider接口就可以使用Linq To SQL的功能.关于如何去实现的话,上一章也为我们引导了一个方向.LinqToDB框架 ...
- LinqToDB 源码分析——DataContext类
LinqToDB框架是一个轻量级的ORM框架.当然,功能上来讲一定比不上Entity Framework的强大.但是在使用上总让笔者感觉有一点Entity Framework的影子.笔者想过可能的原因 ...
- LinqToDB 源码分析——轻谈Linq查询
LinqToDB框架最大的优势应该是实现了对Linq的支持.如果少了这一个功能相信他在使用上的快感会少了一个层次.本来笔者想要直接讲解LinqToDB框架是如何实现对Linq的支持.写到一半的时候却发 ...
- LinqToDB 源码分析——前言
记得笔者进入公司的时候接触的第一个ORM框架是Entity Framework.为了Entity Framework也看了不些的英文资料(不是笔者装B哦).正式使用三个月后.笔者对他有一个全面性的认识 ...
- MyBatis 源码分析——生成Statement接口实例
JDBC的知识对于JAVA开发人员来讲在简单不过的知识了.PreparedStatement的作用更是胸有成竹.我们最常见用到有俩个方法:executeQuery方法和executeUpdate方法. ...
随机推荐
- Asp.net MVC 传递数据 从前台到后台,包括单个对象,多个对象,集合
今天为大家分享下 Asp.net MVC 将数据从前台传递到后台的几种方式. 环境:VS2013,MVC5.0框架 1.基本数据类型 我们常见有传递 int, string, bool, double ...
- 8.仿阿里云虚拟云服务器的FTP(包括FTP文件夹大小限制)
平台之大势何人能挡? 带着你的Net飞奔吧!:http://www.cnblogs.com/dunitian/p/4822808.html#iis 原文:http://dnt.dkill.net/Ar ...
- UWP开发之Mvvmlight实践八:为什么事件注销处理要写在OnNavigatingFrom中
前一段开发UWP应用的时候因为系统返回按钮事件(SystemNavigationManager.GetForCurrentView().BackRequested)浪费了不少时间.现象就是在手机版的详 ...
- ASP.NET中常用的优化性能的方法
1. 数据库访问性能优化 数据库的连接和关闭 访问数据库资源需要创建连接.打开连接和关闭连接几个操作.这些过程需要多次与数据库交换信息以通过身份验证,比较耗费服务器资源.ASP.NET中提供了连接池( ...
- MementoPattern(备忘录模式)
/** * 备忘录模式 * @author TMAC-J * 用于存储bean的状态 */ public class MementoPattern { public class Memento{ pr ...
- 记一次.NET代码重构
好久没写代码了,终于好不容易接到了开发任务,一看时间还挺充足的,我就慢慢整吧,若是遇上赶进度,基本上直接是功能优先,完全不考虑设计.你可以认为我完全没有追求,当身后有鞭子使劲赶的时候,神马设计都是浮云 ...
- Lucene4.4.0 开发之排序
排序是对于全文检索来言是一个必不可少的功能,在实际运用中,排序功能能在某些时候给我们带来很大的方便,比如在淘宝,京东等一些电商网站我们可能通过排序来快速找到价格最便宜的商品,或者通过排序来找到评论数最 ...
- Mono 3.2.3 Socket功能迎来一稳定的版本
由于兴趣自己业余时间一直在搞.net下面的通讯应用,mono的存在得以让.NET程序轻松运行在Linux之下.不过经过多尝试Socket相关功能在Mono下的表现并不理想.不管性能还是吞吐能力方面离我 ...
- 进程监控工具supervisor 启动Mongodb
进程监控工具supervisor 启动Mongodb 一什么是supervisor Superviosr是一个UNIX-like系统上的进程监控工具. Supervisor是一个Python开发的cl ...
- Thinking in Unity3D:渲染管线中的Rendering Path
关于<Thinking in Unity3D> 笔者在研究和使用Unity3D的过程中,获得了一些Unity3D方面的信息,同时也感叹Unity3D设计之精妙.不得不说,笔者最近几年的 ...