不可不知的表达式树(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的人往往觉得表达式树很不容易理解.通过这篇文章我希望大家看到它其实并不像想象中那么难.您只要有普通 ...
随机推荐
- macos + vs code + grep 进行多文件搜索
macos下,打开vs code后,选择view菜单,点击terminal, 在vs code中打开控制台.执行以下grep命令: grep -w "p" -n -r /User ...
- oracle利用job创建一个定时任务,定时调用存储过程
--创建表 create table TESTWP ( ID ), C_DATE DATE ); select * from TESTWP; --2.创建一个sequence create seque ...
- (一)V4L2学习流程
title: V4L2学习流程 date: 2019/4/23 18:00:00 toc: true --- V4L2学习流程 参考资料 关键资料,插图让人一下子就理解了 Linux摄像头驱动1--v ...
- 虚拟代理模式-Virtual Proxy(Java实现)
虚拟代理模式-Virtual Proxy 虚拟代理模式(Virtual PRoxy)会推迟真正所需对象实例化时间. 在需要真正的对象工作之前, 如果代理对象能够处理, 那么暂时不需要真正对象来出手. ...
- CentOS Linux change IP Address
1.change network card configure edit: vi /etc/sysconfig/network-scripts/ifcfg-eth0 ps:notice HWADDR! ...
- reduce/filter/map/zip/isinstance/list列表推导式
- webpack的按需引入配置
ant.design插件需要less配合,yarn add babel-plugin-import,webpack4.0的babel文件已经配置到webpackconfig.js中,需要eject暴露 ...
- CDH5.16.1的maven依赖版本查询地址
1查询官网地址,提供了详细的各个版本的jar依赖版本信息 https://www.cloudera.com/documentation/enterprise/release-notes/topics/ ...
- 美图秀秀api实现图片的裁剪及美化
美图秀秀不仅有PC版.手机版等客户端的软件,还有Web开方接口,可以在web页面上调用美图秀秀的api接口,实现图片的编辑.像淘宝.网易.qq空间.新浪微博等大厂都使用过该接口. 官网地址:http: ...
- word20161229
1. launch 英[lɔ:ntʃ]美[lɔntʃ, lɑntʃ]vt. 发射; 发动; [计算机]开始(应用程序); 开展(活动.计划等);vi. 投入; 着手进行; 热衷于…;n. 投掷; 大船 ...