前面我们说到利用表达式树技术实现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. 1.7分布式工具配置及安装(仅供学习Xshell,VMware)

    前言 最近因为换工作以及其他的一些琐事,耽误了更博时间,再加上分布式的这几个软件之前没撸过....这学习这几个工具上也花了点时间 本篇博客为后续分布式的学习提供基础的安装和配置. 首先,系统为Cent ...

  2. BST(二叉搜索树)相关

    1.BST的合法性:validate-binary-search-tree class TreeNode { int val; TreeNode left; TreeNode right; TreeN ...

  3. AE二次开发中几个功能速成归纳(符号设计器、创建要素、图形编辑、属性表编辑、缓冲区分析)

    /* * 实习课上讲进阶功能所用文档,因为赶时间从网上抄抄改改,凑合能用,记录一下以备个人后用. * * ----------------------------------------------- ...

  4. 关于HashSet集合add元素

    HashSet集合add元素底层实现使用的是HashMap. 简单记忆:无论HashMap put元素还是HashSet add元素,都先调用hashCode()方法,若hashCode方法返回值不同 ...

  5. linux文件常用命令

    文件管理不外乎文件或目录的创建.删除.查询.移动,有mkdir/rm/mv 2.1. 创建和删除 创建:mkdir 删除:rm 删除非空目录:rm -rf file目录 删除日志 rm *log (等 ...

  6. egret中三种单利的写法。

    1 普通的单例写法 缺点:每个单例类里都要写instance和getInstance. class Single{ private static instance:Single; public sta ...

  7. JavaFX - 富互联网应用

    JavaFX教程™ --必看https://www.yiibai.com/javafx /================= 富互联网应用 是那些提供与Web应用程序类似的功能,并可作为桌面应用程序体 ...

  8. PyCharm 项目删除

    Pycharm 删除项目具体操作如下: 1.选择菜单 File   close project 2.选择要删除的项目右上角选择× 3.找到项目所在目录,删除相应文件夹 之后再次打开pycharm 发现 ...

  9. 内地视频网站对各种浏览器HTML5的支持情况

    实在闲得蛋疼 2017/10/1

  10. 发送邮件工具类MailHelper

    using System; using System.Net; using System.Net.Mail; using System.Text; using System.Threading; na ...