前面我们说到利用表达式树技术实现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. 不可不知的表达式树(1)Expression初探

    说起Lambda表达式,大家基本都很熟悉了,而表达式树(Expression Trees),则属于80%的工作中往往都用不到的那种技术,所以即便不是什么新技术,很多人对其理解都并不透彻.此文意图从表达 ...

  2. 再讲IQueryable<T>,揭开表达式树的神秘面纱

    接上篇<先说IEnumerable,我们每天用的foreach你真的懂它吗?> 最近园子里定制自己的orm那是一个风生水起,感觉不整个自己的orm都不好意思继续混博客园了(开个玩笑).那么 ...

  3. 【转】再讲IQueryable<T>,揭开表达式树的神秘面纱

    [转]再讲IQueryable<T>,揭开表达式树的神秘面纱 接上篇<先说IEnumerable,我们每天用的foreach你真的懂它吗?> 最近园子里定制自己的orm那是一个 ...

  4. LinqToDB 源码分析——处理表达式树

    处理表达式树可以说是所有要实现Linq To SQL的重点,同时他也是难点.笔者看完作者在LinqToDB框架里面对于这一部分的设计之后,心里有一点不知所然.由于很多代码没有文字注解.所以笔者只能接合 ...

  5. LinqToDB 源码分析——生成表达式树

    当我们知道了Linq查询要用到的数据库信息之后.接下就是生成对应的表达式树.在前面的章节里面笔者就已经介绍过.生成表达式树是事实离不开IQueryable<T>接口.而处理表达式树离不开I ...

  6. 说说lambda表达式与表达式树(未完)

    Lambda表达式可以转换成为代码(委托)或者数据(表达式树).若将其赋值给委托,则Lambda表达式将转换为IL代码:如果赋值给 Expression<TDelegate>,则构造出一颗 ...

  7. LinQ实战学习笔记(三) 序列,查询操作符,查询表达式,表达式树

    序列 延迟查询执行 查询操作符 查询表达式 表达式树 (一) 序列 先上一段代码, 这段代码使用扩展方法实现下面的要求: 取进程列表,进行过滤(取大于10M的进程) 列表进行排序(按内存占用) 只保留 ...

  8. C#在泛型类中,通过表达式树构造lambda表达式

    场景 最近对爬虫的数据库架构做调整,需要将数据迁移到MongoDB上去,需要重新实现一个针对MongoDB的Dao泛型类,好吧,动手开工,当实现删除操作的时候问题来了. 我们的删除操作定义如下:voi ...

  9. 转载:C#特性-表达式树

    原文地址:http://www.cnblogs.com/tianfan/ 表达式树基础 刚接触LINQ的人往往觉得表达式树很不容易理解.通过这篇文章我希望大家看到它其实并不像想象中那么难.您只要有普通 ...

随机推荐

  1. SDOI2019游记

    Day0 一大早就起床,结果忙活了整整一上午. 12:20从gryz出发,路上发现把耳机和笔忘另一个背包里了(都怪老爸非得让我换背包),15:30差不多就到山师了. 山师也是蛮漂亮的,花开得挺好.到处 ...

  2. MFC:位图和图标的设置

    一. 图标的设置 加载图标   API函数:AfxGetApp()->LoadIconW(); 2. 显示图标 API函数:SetClassLong(); 函数原型:DWORD WINAPI S ...

  3. 3-ftp搭建成功,服务器能访问,外网无法连接和访问

    登录 ECS 管理控制台,找到相应的实例. 在实例的右侧单击管理,进入实例详情页面.选择本实例安全组. 在安全组列表页面,找到相应的安全组,单击配置规则. 在安全组规则页面,单击添加安全组规则. 在添 ...

  4. 八.django模型系统(二)之常用查询及表关系的实现

    Ⅰ.常用查询  1.几个概念 每一个django模型类,都有一个默认的管理器,objects,查询就是依赖于objects管理器进行的(在创建时就被添加了). QuerySet表示数据库中对象的列表( ...

  5. Python的安装与小程序的编写

    Python的安装 在此之前,我完全不了解Python,为了完成任务,在慌忙之中了解了一下Python,通过百度,一步步安装好Python 过程 1.从官网中找到下载菜单并下载最新版本 2.双击pyt ...

  6. js重点--原型链继承详解

    上篇说过了关于原型链继承的问题,这篇详解一下. 1. function animals(){ this.type = "animals"; } animals.prototype. ...

  7. Python高级笔记(二) -- 深拷贝和浅拷贝

    1. 深拷贝 1.1 类型1 注意: d没有改变, 因为d所拷贝的数据没有改变, 而是c往后添加数据. 1.2 类型2: 元组 如果copy.copy拷贝的是元组是深拷贝! 不会进行浅拷贝, 仅仅是指 ...

  8. 半导体知识讲解:IC基础知识及制造工艺流程

    本文转载自微信公众号 - 中国半导体论坛  , 链接 https://mp.weixin.qq.com/s/VhCsVGyEDrgc2XJ0jxLvaA

  9. Spring AOP中 pointcut expression表达式解析

    任意公共方法的执行: execution(public * *(..)) 任何一个以“set”开始的方法的执行: execution(* set*(..)) AccountService 接口的任意方 ...

  10. Contest2162 - 2019-3-28 高一noip基础知识点 测试5 题解版

    传送门 T1 单调栈 按照b排序 在家每一个物品时,判断一下a和b的关系 如果s[sta[top]].a>=s[i].b,就弹栈 记录所有时候的height,并取最大值 T2 单调栈裸题 单调栈 ...