不可不知的表达式树(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的人往往觉得表达式树很不容易理解.通过这篇文章我希望大家看到它其实并不像想象中那么难.您只要有普通 ...
随机推荐
- mac下安装maven
在mac下 使用 brew安装,brew install maven 查看maven版本 mvn -version 打开Terminal,输入以下命令,设置Maven classpath 添加下列两行 ...
- SSM框架中,controller的action返回参数给vue.js
在SSM框架中,controller的action中,返回的是视图,即jsp页面或是ModelAndView,若是通过axios给vue传值的话,需要转换为字符串或是user实体类对象. 使用@Res ...
- 打开MCMC(马尔科夫蒙特卡洛)的黑盒子 - Pymc贝叶斯推理底层实现原理初探
我们在这篇文章里有尝试讨论三个重点.第一,讨论的 MCMC.第二,学习 MCMC 的实现过程,学习 MCMC 算法如何收敛,收敛到何处.第三,将会介绍为什么从后验分布中能返回成千上万的样本,也许读者和 ...
- 定时调度系列之Quartz.Net详解
一. 背景 我们在日常开发中,可能你会遇到这样的需求:"每个月的3号给用户发信息,提醒用户XXX "."每天的0点需要统计前一天的考勤记录"."每个月 ...
- Spring cloud gateway
==================================为什么需要API gateway?==================================企业后台微服务互联互通, 因为 ...
- Java方法参数的传递方式
程序设计语言中,将参数传递给方法(或函数)有两种方法.按值传递(call by value)表示方法接受的是调用者提供的值:按引用调用(call by reference)表示方法接受的是调用者提供的 ...
- Swift 4 经典数据结构 Data Struct大全
快速看看吧,看看大神是如何写出最swifty的算法.我先fork一下,以表敬意. https://github.com/Imputes/swift-algorithm-club
- springMVC中数据流解析与装载
最近在看springmvc原理时,看到一篇比较赞的博文,留存学习,如果侵权,请告知,立删. 地址: https://my.oschina.net/lichhao/blog/172562
- 转载:C++中堆和栈的区别
C++中堆和栈的区别,自由存储区.全局/静态存储区和常量存储区 文章来自一个论坛里的回帖,哪个论坛记不得了! 在C++中,内存分成5个区,他们分别是堆.栈.自由存储区.全局/静态存储区和 ...
- Microsoft Internet Explorer v11 / XML External EntityInjection 0day
[+] Credits: John Page (aka hyp3rlinx) [+] Website: hyp3rlinx.altervista.org[+] Source: http://hyp3 ...