不可不知的表达式树(3)定制IQueryProvider
前面我们说到利用表达式树技术实现LINQ-to-SQL,实际上可以针对任何数据源,实现LINQ-to-Everything。这里还涉及到两个重要的接口即IQueryable和IQueryProvider,这些一起为实现通过LINQ访问各种数据源提供了统一的编程接口。
一、认识IQueryable<T>
接口源码如下:
namespace System.Linq
{
//提供对未指定数据类型的特定数据源的查询进行计算的功能。
public interface IQueryable : IEnumerable
{
//获取与 System.Linq.IQueryable 的实例关联的表达式树。
Expression Expression { get; }
//获取在执行与 System.Linq.IQueryable 的此实例关联的表达式树时返回的元素的类型。
Type ElementType { get; }
//获取与此数据源关联的查询提供程序。
IQueryProvider Provider { get; }
}
public interface IQueryable<out T> : IEnumerable<T>, IQueryable, IEnumerable
{
}
}
IQueryable中定义了三个只读的属性,ElementType即为查询对象的类型,Expression即为表达式树,我们的Linq查询表达式都将转换为表达式树,而Provider表示数据源查询提供程序,将Expression翻译为数据源的查询语言,如Sql,并负责最终的数据查询实现。所以我们必须要实现IQueryProvider接口。
二、IQueryProvider
接口源码如下:
namespace System.Linq
{
public interface IQueryProvider
{
IQueryable CreateQuery(Expression expression);
IQueryable<TElement> CreateQuery<TElement>(Expression expression);
object Execute(Expression expression);
TResult Execute<TResult>(Expression expression);
}
}
IQueryProvider接口包含两组方法,CreateQuery顾名思义即是由Provider通过传入的Expression参数创建并返回一个新的IQueryable查询实例。
Execute则是真正解析Expression的,解析即遍历并翻译其为特定查询语言的过程,然后进行查询,并返回查询结果object。
三、定制IQueryProvider
定制查询我们自己的数据源,比如最终实现如下的代码调用方式,该如何实现呢?
class Program
{
static void Main(string[] args)
{
var provider = new MyQueryProvider();
var queryable = new Query<Person>(provider); var query =
from p in queryable
where p.Age >= &&
p.Gender== “男”
select p; var list = query.ToList();
Console.ReadLine();
}
}
下面先给出这段代码执行的时序图来帮助我们进一步理解整个执行过程:
代码5-6行,对应步骤1-4,实例化了我们自定义的IQueryable;
代码8-12行,对应步骤5-8,在IQueryable实例基础上,进行Linq查询,查询表达式翻译为Expression,构造出新的IQueryable实例;
代码14行,对应步骤9-12,ToList()方法调用,延迟加载执行,查询出结果并返回。
从中可以看出我们的类图大概是这样的:
大体框架就是如此,然后我们需要做的最重要的事情就是实现MyQueryProvider的Execute(),在这个核心方法里解析Expression,翻译为特定数据源的查询语句,并进行查询。
实例代码:
public override object Execute(Expression expression)
{
//遍历表达式树,生成特定数据源的查询语句,例如sql
String myLang = new MyExpressionVisitor().ProcessExpression(expression);
//根据查询语句进行查询得到结果
IEnumerable<Person> results = PersonHelper.DoQuery(myLang);
return results;
}
至于第6行代码,根据查询语句查询出结果,就是各种Helper的职责了,例如SqlHelper。
然后最重要的,还是ProcessExpression(expression)的实现了,换句话说归根结底这个IQueryProvider最核心的功能就是遍历表达式树。
最后给出主要的遍历代码:
public class MyExpressionVisitor
{
private string _myLang;
// 入口方法
public string ProcessExpression(Expression expression)
{
_myLang = string.Empty;
VisitExpression(expression);
return _myLang;
}
private void VisitExpression(Expression expression)
{
switch (expression.NodeType)
{
// 访问 &&
case ExpressionType.AndAlso:
VisitAndAlso((BinaryExpression)expression);
break;
// 访问 等于
case ExpressionType.Equal:
VisitEqual((BinaryExpression)expression);
break;
// 访问 小于和小于等于
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
VisitLessThanOrEqual((BinaryExpression)expression);
break;
// 访问大于和大于等于
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
GreaterThanOrEqual((BinaryExpression)expression);
break;
// 访问调用方法,主要用于解析Contains方法
case ExpressionType.Call:
VisitMethodCall((MethodCallExpression)expression);
break;
// 访问Lambda表达式
case ExpressionType.Lambda:
VisitExpression(((LambdaExpression)expression).Body);
break;
}
} // 访问 &&
private void VisitAndAlso(BinaryExpression andAlso)
{
VisitExpression(andAlso.Left);
VisitExpression(andAlso.Right);
} // 访问 等于
private void VisitEqual(BinaryExpression expression)
{
//...
} // 访问大于等于
private void GreaterThanOrEqual(BinaryExpression expression)
{
//...
} // 访问 小于和小于等于
private void VisitLessThanOrEqual(BinaryExpression expression)
{
//...
} // 访问 方法调用
private void VisitMethodCall(MethodCallExpression expression)
{
//...
} // 获取属性值
private Object GetMemberValue(MemberExpression memberExpression)
{
MemberInfo memberInfo;
Object obj; if (memberExpression == null)
throw new ArgumentNullException("memberExpression"); if (memberExpression.Expression is ConstantExpression)
obj = ((ConstantExpression)memberExpression.Expression).Value;
else if (memberExpression.Expression is MemberExpression)
obj = GetMemberValue((MemberExpression)memberExpression.Expression);
else
throw new NotSupportedException("Expression type not supported: "
+ memberExpression.Expression.GetType().FullName); memberInfo = memberExpression.Member;
if (memberInfo is PropertyInfo)
{
PropertyInfo property = (PropertyInfo)memberInfo;
return property.GetValue(obj, null);
}
else if (memberInfo is FieldInfo)
{
FieldInfo field = (FieldInfo)memberInfo;
return field.GetValue(obj);
}
else
{
throw new NotSupportedException("MemberInfo type not supported: "
+ memberInfo.GetType().FullName);
}
}
}
至此,表达式树系列文章完结,希望对大家有所帮助。
文章参考: The Wayward WebLog.
不可不知的表达式树(3)定制IQueryProvider的更多相关文章
- 不可不知的表达式树(1)Expression初探
说起Lambda表达式,大家基本都很熟悉了,而表达式树(Expression Trees),则属于80%的工作中往往都用不到的那种技术,所以即便不是什么新技术,很多人对其理解都并不透彻.此文意图从表达 ...
- 再讲IQueryable<T>,揭开表达式树的神秘面纱
接上篇<先说IEnumerable,我们每天用的foreach你真的懂它吗?> 最近园子里定制自己的orm那是一个风生水起,感觉不整个自己的orm都不好意思继续混博客园了(开个玩笑).那么 ...
- 【转】再讲IQueryable<T>,揭开表达式树的神秘面纱
[转]再讲IQueryable<T>,揭开表达式树的神秘面纱 接上篇<先说IEnumerable,我们每天用的foreach你真的懂它吗?> 最近园子里定制自己的orm那是一个 ...
- LinqToDB 源码分析——处理表达式树
处理表达式树可以说是所有要实现Linq To SQL的重点,同时他也是难点.笔者看完作者在LinqToDB框架里面对于这一部分的设计之后,心里有一点不知所然.由于很多代码没有文字注解.所以笔者只能接合 ...
- LinqToDB 源码分析——生成表达式树
当我们知道了Linq查询要用到的数据库信息之后.接下就是生成对应的表达式树.在前面的章节里面笔者就已经介绍过.生成表达式树是事实离不开IQueryable<T>接口.而处理表达式树离不开I ...
- 说说lambda表达式与表达式树(未完)
Lambda表达式可以转换成为代码(委托)或者数据(表达式树).若将其赋值给委托,则Lambda表达式将转换为IL代码:如果赋值给 Expression<TDelegate>,则构造出一颗 ...
- LinQ实战学习笔记(三) 序列,查询操作符,查询表达式,表达式树
序列 延迟查询执行 查询操作符 查询表达式 表达式树 (一) 序列 先上一段代码, 这段代码使用扩展方法实现下面的要求: 取进程列表,进行过滤(取大于10M的进程) 列表进行排序(按内存占用) 只保留 ...
- C#在泛型类中,通过表达式树构造lambda表达式
场景 最近对爬虫的数据库架构做调整,需要将数据迁移到MongoDB上去,需要重新实现一个针对MongoDB的Dao泛型类,好吧,动手开工,当实现删除操作的时候问题来了. 我们的删除操作定义如下:voi ...
- 转载:C#特性-表达式树
原文地址:http://www.cnblogs.com/tianfan/ 表达式树基础 刚接触LINQ的人往往觉得表达式树很不容易理解.通过这篇文章我希望大家看到它其实并不像想象中那么难.您只要有普通 ...
随机推荐
- Xcode10 不能导入头文件(导入头文件不提示)
连接地址:https://blog.csdn.net/wyz670083956/article/details/87774705 xcode10可能是:Project Settings
- hadoop记录-Hadoop参数汇总
Hadoop参数汇总 linux参数 以下参数最好优化一下: 文件描述符ulimit -n 用户最大进程 nproc (hbase需要 hbse book) 关闭swap分区 设置合理的预读取缓冲区 ...
- Java裸写爬虫技术,运用多线程技术,高效爬取某个医疗机构网站数据
最近喜欢上了数据的庞大的感觉,就爬取了一下某个医疗机构网站医疗数据,由于数据量庞大,只爬取了江西省的各个市的各个医院的各个科室的各个科室.中各种信息.其中用的持久层技术是hibernate框架,和用到 ...
- js关于“call方法”百度,阿里超难面试题
面试题:function fn(a,b){ console.log(this); console.log(a); console.log(a+b);}fn.call(1);fn.ca ...
- 路径规划算法之Bellman-Ford算法
最近由于工作需要一直在研究Bellman-Ford算法,这也是我第一次用C++编写代码. 1.Bellman-Ford算法总结 (1)Bellman-Ford算法计算从源点(起始点)到任意一点的最短路 ...
- kettle 分组
kettle 分组组可以实现group_concat的效果
- 获取spring的IOC核心容器,并根据id获取对象
public class Client { /** * 获取spring的IOC核心容器,并根据id获取对象 * ApplicationContext的三个常用实现类 * classPathXmlAp ...
- sql注入--双查询报错注入原理探索
目录 双查询报错注入原理探索 part 1 场景复现 part 2 形成原因 part 3 报错原理 part 4 探索小结 双查询报错注入原理探索 上一篇讲了双查询报错查询注入,后又参考了一些博客, ...
- 只要三步,使用html5+js实现像素风头像生成器
只要三步,使用html5+js实现像素风头像生成器 html5的画布给我们带来了很大的空间,其实像素风格头像生成器只是用到了画方块的方法.画一个像素头像,只要三步,1.解决像素点,2.解决像素点之间的 ...
- android 给view添加阴影
1.方法一: 使用 CardView 布局 <android.support.v7.widget.CardView xmlns:android="http://schemas.andr ...