LinqToDB 源码分析——轻谈Linq查询

时间:2022-11-13 08:20:05

LinqToDB框架最大的优势应该是实现了对Linq的支持。如果少了这一个功能相信他在使用上的快感会少了一个层次。本来笔者想要直接讲解LinqToDB框架是如何实现对Linq的支持。写到一半的时候却发现本系列在内容上的引导显得格外的生硬。思考在三最后还是决定在讲解LinqToDB框架之前来一章过度文。

Linq查询的原理


我们在学习Linq的时候会见到一些很常见的关键词语。比如Linq To SQL、Linq To Objects、Linq To XML等。事实这些一般都是根据不同的数据源来进行命名的。 说实话笔者当初学习的时候,看到这些命名险些以为只有这几种。事实不是这样子的。Linq有俩个核心类——Enumerable类和Queryable类。这俩个类可以说贯穿整个Linq知识体系。如果有心的朋友可以点开对应的dll包就是发现他们都在System.Linq命名空间下。同时他们都是用于扩展相应的静态方法。而且方法名大至相同。然后他们却在本质上有着细微的差别。Enumerable类是对IEnumerable<T>接口进行扩展并且传入了Func类型的参数。数据源是来自于内存中的。而Queryable类是对IQueryable<T>接口进行扩展,传入参数是表达式(Expression类型)。数据源是来自于第三方。比如SQL Server、MySql等。

Linq的思想就是提供一个统一模型操作来处理数据。所以本质来讲对数据源不是很讲究。比如数据源是文件,或则说数据源是Excel之类的。相信可能有人已经看到过Linq To Excel呢?主要辛苦还是这些开发底层的人。对于使用者来讲没有什么多大的差别。Linq现在面对数据源而扩展功能有很多。其中专对数据库来讲,最流行还是有Linq To SQL。而且扩展数据库的Linq功能大多数都用IQueryable<T>接口。当然,这不是说用IEnumerable<T>接口就不行了。只是这俩种接口在实现上有着很大的差别。IEnumerable<T>接口我们都知道他一般是专对于内存的。这意味着我们必须把相应的数据全部加载到内存中才可以进行查询。这样子的操作太伤性能了。而IQueryable<T>接口我们可以巧妙的用上表达式树(Expression Tree)进行转化生成对应的数据库SQL语句,然后在执行数据库。这才是显得合理。

ORM思想能流行大体上可以说是因为他的思想更加贴切于人类的思维方式。在笔者看来如果把Linq技术说成也是ORM思想的产物之一,这样子的说法也不为过。这也是笔者喜欢Linq的地方。LinqToDB框架只所以都能支持Linq。不可否认也是依据这一种上面所讲的原理来实现的。大体的想法如下。

  1. 实现Linq提供的IQueryable<T>接口和IQueryProvider接口。生成相关的表达式树。
  2. 把对应的表达式树转化生成对应数据库的SQL语句。并执行。
  3. 根据映射的信息,生成对应的集合类。(这里的集合类是指SQL语句执行结果转成类放入的集合)

实现自定义的Linq查询一定离不开俩类——IQueryable<T>接口和IQueryProvider接口。上面的工作可以说都在这俩类上面。IQueryable<T>接口一般用于生成对应的表达式树。而IQueryProvider接口用于执行表达式树,转化成对应的SQL语句,执行数据库并生成映射的模型对象。

注意:IQueryable<T>接口是什么样子生成相关的表达式树呢?让笔者来讲的话,笔者觉得有一点浪费时间。但是不要当心博客园里面有一位大神写的博文一定能满足你——王清培的《.NET深入解析LINQ框架》

实现Linq查询


支持Linq查询本意上就是实现上面所讲的俩个接口。当你实现IQueryable<T>接口的时候,VS提示你实现三个属性。如果你F12进去查看他有些什么内容的话,你会发现什么也没有。为了方便笔者还是把他贴出来了。

IQueryable<T>接口:

public interface IQueryable<out T> : IEnumerable<T>, IQueryable, IEnumerable
{
}

IQueryable接口:

public interface IQueryable : IEnumerable
{
Type ElementType { get; }
Expression Expression { get; }
IQueryProvider Provider { get; }
}

这些属性全部来自于IQueryable接口。同时你还会发现——我去!他即然继承了IEnumerable<T>接口。笔者不惊的感叹微软真会玩。ElementType属性的作用也正如他的名字一样子——元素类型。用于指定当前要查询的是什么类型的数据。对于Expression属性的话,也正如上面笔者讲到IQueryable<T>接口会为我们建立一个表达式树。Expression属性便是用于建立表达式树的。最后一个Provider属性。他是IQueryProvider类型的。用于执行表达式生成对应的数据库SQL语句和执行数据库。

IQueryProvider接口有四个方法。事实上应该说是俩个才对。因为他们是俩俩功能相同。IQueryable<T>接口生成完表达式树之后,最终执行的有俩个方法一个是来IEnumerable<T>的GetEnumerator方法,一个是来IQueryProvider接口的Execute方法。那么笔者上面讲到的“执行表达式生成对应的数据库SQL语句和执行数据库”的功能也是在这俩个方法中实现的。

好了。笔者说在多也没有什么用。不如笔者写一个小小的应用来说明这一切。如下

  public class AomiQuery<T>:IOrderedQueryable<T>
{
public AomiQuery()
{
this.Expression = System.Linq.Expressions.Expression.Constant(this);
this.Provider = new AomiQueryProvider();
}
public AomiQuery(Expression expression, IQueryProvider provider)
{
this.Expression = expression;
this.Provider = provider;
}
public IEnumerator<T> GetEnumerator()
{
return (Provider.Execute<IEnumerable<T>>(Expression)).GetEnumerator();
} System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return (Provider.Execute<System.Collections.IEnumerable>(Expression)).GetEnumerator();
} public Type ElementType
{
get { return typeof(T); }
} public Expression Expression { private set; get; } public IQueryProvider Provider { private set; get; }
}

上面这段代码中,笔者用是不是IQueryable<T>接口而是IOrderedQueryable<T>接口。事实上不有多大的差别。IOrderedQueryable<T>接口是继承IQueryable<T>接口。官方的说法是IQueryable<T>接口不能实现Order by功能。IOrderedQueryable<T>接口却可以。当然关于这一点,有兴趣的读者们可以自行去看看。

IQueryable<T>接口实现完了。让我们在实现一下IQueryProvider接口吧。

  public class AomiQueryProvider:IQueryProvider
{ public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new AomiQuery<TElement>(expression, this);
} public IQueryable CreateQuery(System.Linq.Expressions.Expression expression)
{
Type elementType = TypeSystem.GetElementType(expression.Type);
try
{
return (IQueryable)Activator.CreateInstance(typeof(AomiQuery<>).MakeGenericType(elementType), new object[] { this, expression });
}
catch (System.Reflection.TargetInvocationException tie)
{
throw tie.InnerException;
}
} public TResult Execute<TResult>(Expression expression)
{
bool IsEnumerable = (typeof(TResult).Name == "IEnumerable`1");
return (TResult)this.ExecuteReader(expression,IsEnumerable);
} public object Execute(Expression expression)
{
return this.ExecuteReader(expression);
} public object ExecuteReader(Expression expression, bool isEnumerable = false)
{
if (expression is MethodCallExpression)
{
MethodCallExpression mce = expression as MethodCallExpression;
SqlConnection connection = new SqlConnection("Data Source=.;Initial Catalog=Northwind;Persist Security Info=True;User ID=sa;Password=123");
SqlCommand command = new SqlCommand();
command.Connection = connection; StringBuilder commandText = new StringBuilder(); 44 if (mce != null && mce.Method.DeclaringType == typeof(Queryable) && mce.Method.Name == "Where")
45 {
46 commandText.Append("SELECT * FROM ");
47
48 ConstantExpression ce = mce.Arguments[0]as ConstantExpression;
49 IQueryable queryable = ce.Value as IQueryable;
50
51 commandText.Append(queryable.ElementType.Name);
52 commandText.Append(" WHERE ");
53
54 UnaryExpression ue = mce.Arguments[1] as UnaryExpression;
55 LambdaExpression lambda = ue.Operand as LambdaExpression;
56 BinaryExpression be = lambda.Body as BinaryExpression;
57 MemberExpression lme = be.Left as MemberExpression;
58 ConstantExpression rce = be.Right as ConstantExpression;
59
60 commandText.Append(lme.Member.Name);
61
62 switch (be.NodeType)
63 {
64 case ExpressionType.And:
65 commandText.Append(" AND ");
66 break;
67 case ExpressionType.Or:
68 commandText.Append(" OR ");
69 break;
70 case ExpressionType.Equal:
71 commandText.Append(" = ");
72 break;
73 case ExpressionType.NotEqual:
74 commandText.Append(" <> ");
75 break;
76 case ExpressionType.LessThan:
77 commandText.Append(" < ");
78 break;
79 case ExpressionType.LessThanOrEqual:
80 commandText.Append(" <= ");
81 break;
82 case ExpressionType.GreaterThan:
83 commandText.Append(" > ");
84 break;
85 case ExpressionType.GreaterThanOrEqual:
86 commandText.Append(" >= ");
87 break;
88 }
89
90 commandText.Append(rce.Value);
91
92 } command.CommandText = commandText.ToString(); List<Products> proList = new List<Products>(); connection.Open(); SqlDataReader dr = command.ExecuteReader(); while (dr.Read())
{
Products product = new Products();
product.ProductID = Convert.ToInt32(dr["ProductID"]);
product.ProductName = Convert.ToString(dr["ProductName"]);
proList.Add(product);
} dr.Close();
connection.Close(); return isEnumerable ? proList.AsEnumerable() : proList; } return null;
}

在笔者看来大量的工作都放在了IQueryProvider接口上面,IQueryable<T>接口显得更加像一个帮手——就是帮我们建表达式树。IQueryProvider接口里面有俩方法叫CreateQuery。从代码中我们就可以看到他会返回一个新的IQueryable<T>接口实例。由于笔者这边只是写一个where功能。如果有order by功能的话,那么CreateQuery方法会被在调用一次。同时传入where过程中生成的表达式。当然这个时候我们还要在新建一个IQueryable<T>接口实例。注意每一次新建IQueryable<T>接口实例都会把上一个的表达式传入到实例的Expression属性。具体的情况,我觉得读者们可以自己做一个小试验来看看会比较好。如果你看过王大神的博文的话,相信这个一定不在话下。上面红色的代码是笔者处理表达式树的。在正式开发过程中往往是不可能这样子做的。笔者这边只是想要表达一个意思。在执行数据库之前,一定要先转化成对应的SQL。

执行代码:

static void Main(string[] args)
{
AomiQuery<Products> aomiProducts = new AomiQuery<Products>();
var query = from p in aomiProducts where p.ProductID > select p;
List<Products> proList = query.ToList(); foreach (Products p in proList)
{
Console.WriteLine("ProductID:{0} ----------------> ProductName:{1}", p.ProductID, p.ProductName);
} Console.ReadKey();
}

执行结果:

LinqToDB 源码分析——轻谈Linq查询

笔者小语


这一篇文章笔者写了俩遍。笔者第一次写的时候,把他写成Linq的教程去了。等结束的时候才明白我为什么要写Linq呢?我只要写跟LinqToDB相关的知识就可以了。最后删 了重来一遍。相当的无语。

LinqToDB 源码分析——轻谈Linq查询的更多相关文章

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

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

  2. LinqToDB 源码分析——生成与执行SQL语句

    生成SQL语句的功能可以算是LinqToDB框架的最后一步.从上一章中我们可以知道处理完表达式树之后,相关生成SQL信息会被保存在一个叫SelectQuery类的实例.有了这个实例我们就可以生成对应的 ...

  3. LinqToDB 源码分析——前言

    记得笔者进入公司的时候接触的第一个ORM框架是Entity Framework.为了Entity Framework也看了不些的英文资料(不是笔者装B哦).正式使用三个月后.笔者对他有一个全面性的认识 ...

  4. LinqToDB 源码分析——DataContext类

    LinqToDB框架是一个轻量级的ORM框架.当然,功能上来讲一定比不上Entity Framework的强大.但是在使用上总让笔者感觉有一点Entity Framework的影子.笔者想过可能的原因 ...

  5. LinqToDB 源码分析——设计原理

    我们知道实现了IQueryable<T>接口和IQueryProvider接口就可以使用Linq To SQL的功能.关于如何去实现的话,上一章也为我们引导了一个方向.LinqToDB框架 ...

  6. jQuery 2&period;0&period;3 源码分析Sizzle引擎 - 高效查询

    为什么Sizzle很高效? 首先,从处理流程上理解,它总是先使用最高效的原生方法来做处理 HTML文档一共有这么四个API: getElementById 上下文只能是HTML文档 浏览器支持情况:I ...

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

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

  8. Linq转换操作之ToArray&comma;ToList&comma;ToDictionary源码分析

    Linq转换操作之ToArray,ToList,ToDictionary源码分析 一:linq中的转换运算符 1. ToArray 我们经常用在linq查询上吧. linq只能运用在IEnumerab ...

  9. Linq特取操作之ElementAt&comma;Single&comma;Last&comma;First源码分析

    Linq特取操作之ElementAt,Single,Last,First源码分析 一:linq的特取操作 First/FirstOrDefault, Last/LastOrDefault, Eleme ...

随机推荐

  1. 【摘】BPMN2&period;0-概要

    BPMN2.0-概要   原文地址:http://www.uml.org.cn/workclass/201206272.asp 作者:AliKevin2011,发布于2012-6-27   一.BPM ...

  2. C&num; Emit动态代理生成一个实体对象

    /// <summary> /// 使用Emit动态代理收集实体信息 /// </summary> /// <typeparam name="T"&g ...

  3. SO从 &bsol;u 这样的字符串 构建对象

    ShowMessage(SO('\u4F18\u8D28\u670D\u52A112').AsString); 正确 得到 优质服务12 ShowMessage(so( 个数字,后面的中文未能解析出.

  4. U盘安装 Windows XP 原版 ISO 的几点心得

    虽然我一直致力于推动最新操作系统的部署,劝说周围朋友尽快淘汰 Windows XP,但还是难免有一些老电脑.老朋友的电脑,坚持要使用 XP 系统. 这里有几点使用U盘安装 Windows XP 原版 ...

  5. 【CSS3】---文本阴影text-shadow

    text-shadow可以用来设置文本的阴影效果. 语法: text-shadow: X-Offset Y-Offset blur color; X-Offset:表示阴影的水平偏移距离,其值为正值时 ...

  6. csuoj 1354&colon; Distinct Subsequences

    这个题是计算不同子序列的和: spoj上的那个同名的题是计算不同子序列的个数: 其实都差不多: 计算不同子序列的个数使用dp的思想: 从头往后扫一遍 如果当前的元素在以前没有出现过,那么dp[i]=d ...

  7. UITableView 隐藏多余的分割线

    //隐藏多余的分割线 - (void)setExtraCellLineHidden: (UITableView *)tableView { UIView *view =[ [UIView alloc] ...

  8. 证明N&equals;&lbrace;1,2,&period;&period;&period;,n,&period;&period;&period;&rcub;最高万元 黄晓宁

    证明N={1,2....,n....}最高万元 黄小宁(通讯:广州市华南师大南区9-303   510631) 5000年数学一直不知{2,3,....n+1,...}(n的变域是N)中"深 ...

  9. PostgreSQL使用MyBatis&comma;insert时返回主键

    MyBatis中普通的insert语句是这样的: <insert id="insert" parameterType="com.xxx.xxx.xxDo" ...

  10. Node&period;js初探之GET方式传输

    Node.js初探之GET方式传输 例子:form用GET方法向后台传东西 html文件: <form action="http://localhost:8080/aaa" ...