<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Vincent&#39;s Site</title>
  
  <subtitle>Be a Software Artist!</subtitle>
  <link href="https://www.liuwj.me/atom.xml" rel="self"/>
  
  <link href="https://www.liuwj.me/"/>
  <updated>2026-03-01T02:56:55.951Z</updated>
  <id>https://www.liuwj.me/</id>
  
  <author>
    <name>刘文俊</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>「招聘」广州 Lazada 招聘 Java/C++ 工程师 (P5/P6)</title>
    <link href="https://www.liuwj.me/posts/lazada-hiring/"/>
    <id>https://www.liuwj.me/posts/lazada-hiring/</id>
    <published>2025-03-28T00:00:00.000Z</published>
    <updated>2026-03-01T02:56:55.951Z</updated>
    
    <content type="html"><![CDATA[<p>广州 Lazada 招聘啦，有意者简历发送到邮箱 <a href="mailto:vincent.liu@lazada.com">vincent.liu@lazada.com</a>，岗位有效期 2025.04.30 截止</p><p>阿里国际数字商业集团 Lazada 首页 &amp; 内容团队，正在热聘 Java、C++ 服务端 P6 岗位，Base 广州，欢迎自荐或推荐</p><span id="more"></span><p>我们正在寻找对技术充满热情的你，共同打造电商领域的智能化未来！无论你是专注于高性能推荐引擎的架构设计与优化，还是热衷于大模型 Agent 的落地应用与推理性能提升，抑或是探索生成式推荐与导购助手的创新场景，这里都有广阔的舞台。我们期待具备扎实计算机基础、熟悉算法与数据结构、热爱解决复杂问题的你加入，一起用技术创新驱动业务变革，定义行业新高度！</p><ul><li><a href="http://aidc-jobs.alibaba.com/off-campus/position-detail?lang=zh&amp;positionId=300045816">岗位 1：LAZADA-Java高级开发工程师-推荐系统方向</a></li><li><a href="http://aidc-jobs.alibaba.com/off-campus/position-detail?lang=zh&amp;positionId=300046013">岗位 2：LAZADA-Java高级开发工程师-LLM应用方向</a></li><li><a href="http://aidc-jobs.alibaba.com/off-campus/position-detail?lang=zh&amp;positionId=300045714">岗位 3：LAZADA-C++高级开发工程师-推荐&amp;大模型引擎方向</a></li></ul><p><img src="/files/in-post/lazada-hiring.jpg" alt="" /></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;广州 Lazada 招聘啦，有意者简历发送到邮箱 &lt;a href=&quot;mailto:vincent.liu@lazada.com&quot;&gt;vincent.liu@lazada.com&lt;/a&gt;，岗位有效期 2025.04.30 截止&lt;/p&gt;
&lt;p&gt;阿里国际数字商业集团 Lazada 首页 &amp;amp; 内容团队，正在热聘 Java、C++ 服务端 P6 岗位，Base 广州，欢迎自荐或推荐&lt;/p&gt;</summary>
    
    
    
    
    <category term="招聘" scheme="https://www.liuwj.me/tags/%E6%8B%9B%E8%81%98/"/>
    
  </entry>
  
  <entry>
    <title>「直播回放」Ktorm：一个让你的数据库操作更具 Kotlin 风味的 ORM 框架｜Kotlin 中文开发者大会</title>
    <link href="https://www.liuwj.me/posts/ktorm-in-kotlin-conf-cn-2024/"/>
    <id>https://www.liuwj.me/posts/ktorm-in-kotlin-conf-cn-2024/</id>
    <published>2025-03-09T00:00:00.000Z</published>
    <updated>2026-03-01T02:56:55.951Z</updated>
    
    <content type="html"><![CDATA[<p>有幸在 2024 年 Kotlin 中文开发者大会上受邀做了一次 Ktorm 的分享，以下是这次分享的回放视频：</p><blockquote><p>作为 Kotlin 服务端开发者，你可能已经厌倦使用 MyBatis 和 Hibernate，希望寻找一款专为 Kotlin 开发的 ORM 框架。而 Ktorm 就是一款专门为 Kotlin 设计，旨在让数据库操作更加流畅、自然，更贴合 Kotlin 语法特性的 ORM 框架。<br><br> 在这场分享里，将跟大家介绍 Ktorm 的核心设计、框架使用和扩展，并通过使用 Ktorm 写出更具 Kotlin 风味的数据库操作代码。听完这场分享后，将会对如何善用 Kotlin 语法来设计 ORM 框架有进一步的认识。<br><br> PPT 下载链接：<a href="/files/in-post/ktorm-in-kotlin-conf-cn-2024.pdf">ktorm-in-kotlin-conf-cn-2024.pdf</a></p></blockquote><span id="more"></span><div style="position: relative; padding: 30% 45%;">    <iframe src="https://player.bilibili.com/player.html?aid=114081109443014&bvid=BV1M693Y9EKD&cid=28620096868&page=1&high_quality=1&danmaku=0" allowfullscreen="allowfullscreen" scrolling="no" frameborder="no" style="position: absolute; width: 100%; height: 100%; left: 0; top: 0;"></iframe></div>]]></content>
    
    
    <summary type="html">&lt;p&gt;有幸在 2024 年 Kotlin 中文开发者大会上受邀做了一次 Ktorm 的分享，以下是这次分享的回放视频：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;作为 Kotlin 服务端开发者，你可能已经厌倦使用 MyBatis 和 Hibernate，希望寻找一款专为 Kotlin 开发的 ORM 框架。而 Ktorm 就是一款专门为 Kotlin 设计，旨在让数据库操作更加流畅、自然，更贴合 Kotlin 语法特性的 ORM 框架。&lt;br&gt;&lt;br&gt; 在这场分享里，将跟大家介绍 Ktorm 的核心设计、框架使用和扩展，并通过使用 Ktorm 写出更具 Kotlin 风味的数据库操作代码。听完这场分享后，将会对如何善用 Kotlin 语法来设计 ORM 框架有进一步的认识。&lt;br&gt;&lt;br&gt; PPT 下载链接：&lt;a href=&quot;/files/in-post/ktorm-in-kotlin-conf-cn-2024.pdf&quot;&gt;ktorm-in-kotlin-conf-cn-2024.pdf&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    
    <category term="Kotlin" scheme="https://www.liuwj.me/tags/Kotlin/"/>
    
    <category term="Ktorm" scheme="https://www.liuwj.me/tags/Ktorm/"/>
    
  </entry>
  
  <entry>
    <title>如何在墙内反代 Gravatar 显示博客头像</title>
    <link href="https://www.liuwj.me/posts/gravatar-reverse-proxy-in-china/"/>
    <id>https://www.liuwj.me/posts/gravatar-reverse-proxy-in-china/</id>
    <published>2021-04-18T00:00:00.000Z</published>
    <updated>2026-03-01T02:56:55.951Z</updated>
    
    <content type="html"><![CDATA[<blockquote class="blockquote-center">生命不息，折腾不止</blockquote><p><a href="https://cn.gravatar.com/">Gravatar</a> 即全球通用头像 (Globally Recognized Avatar) 服务，用户只要在上面上传了自己的头像，那么在所有支持的网站上发帖时，只要提供与这个头像关联的 Email，就可以显示出自己的 Gravatar 头像。可以说是「一次上传，全网通用」~~</p><p>可惜国内的网络环境实在一言难尽，Gravatar 常年都处于无法访问的状态，所以本站一直都是用 v2ex 提供的 CDN 镜像，然而，就在前两周，v2ex 也被墙了，emm… 因为不想再白嫖其他的国内镜像，因此开始考虑自己动手搭建。这个事情其实挺简单的，网上随便搜索一下就有答案，只要有一台墙外的 VPS，用 nginx 给 Gravatar 做个反向代理就好。</p><p>但是，如果你的主机在墙内呢，怎么办？</p><span id="more"></span><h2 id="科学上网"><a class="markdownIt-Anchor" href="#科学上网"></a> 科学上网</h2><p>无论如何，科学上网是第一件必须解决的事情。如果你的主机甚至都不能访问 Gravatar，反向代理根本就无从谈起。要实现科学上网，你首先需要在墙外有可用的 Shadowsocks 服务用于代理你的流量，至于是使用 VPS 自建也好、直接购买机场的服务也好，这里不作探讨。</p><p>安装 Shadowsocks：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">apt-get install python-pip</span><br><span class="line">pip install shadowsocks</span><br></pre></td></tr></table></figure><p>在 <code>/etc/shadowsocks/config.json</code> 目录创建一个配置文件：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;server&quot;</span><span class="punctuation">:</span> <span class="string">&quot;server_ip&quot;</span><span class="punctuation">,</span>         <span class="comment">// 服务器地址</span></span><br><span class="line">  <span class="attr">&quot;server_port&quot;</span><span class="punctuation">:</span> <span class="number">8388</span><span class="punctuation">,</span>           <span class="comment">// 服务器端口</span></span><br><span class="line">  <span class="attr">&quot;local_address&quot;</span><span class="punctuation">:</span> <span class="string">&quot;127.0.0.1&quot;</span><span class="punctuation">,</span>  <span class="comment">// 本地 socks5 服务的监听地址</span></span><br><span class="line">  <span class="attr">&quot;local_port&quot;</span><span class="punctuation">:</span> <span class="number">1080</span><span class="punctuation">,</span>            <span class="comment">// 本地 socks5 服务的监听端口</span></span><br><span class="line">  <span class="attr">&quot;password&quot;</span><span class="punctuation">:</span> <span class="string">&quot;password&quot;</span><span class="punctuation">,</span>        <span class="comment">// 服务器密码</span></span><br><span class="line">  <span class="attr">&quot;timeout&quot;</span><span class="punctuation">:</span> <span class="number">300</span><span class="punctuation">,</span>                <span class="comment">// 超时时间</span></span><br><span class="line">  <span class="attr">&quot;method&quot;</span><span class="punctuation">:</span> <span class="string">&quot;aes-256-gcm&quot;</span><span class="punctuation">,</span>       <span class="comment">// 服务器加密方式</span></span><br><span class="line">  <span class="attr">&quot;fast_open&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>配置完成后，使用 <code>sslocal</code> 命令启动客户端服务：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sslocal -c /etc/shadowsocks/config.json -d start</span><br></pre></td></tr></table></figure><p>这个命令会在本地 1080 端口启动一个 socks5 代理，连接远程的 Shadowsocks 服务，把网络请求转发过去。</p><p>接下来检查一下这个代理是否可用，使用 <code>curl</code> 命令查询一下自己的 IP，<code>--socks5-hostname</code> 参数指定 socks5 代理服务的地址：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">$ curl --socks5-hostname 127.0.0.1:1080 cip.cc</span><br><span class="line">IP      : 104.xxx.xxx.xxx</span><br><span class="line">地址    : 美国  加利福尼亚州  洛杉矶</span><br><span class="line">运营商  : it7.net</span><br><span class="line"></span><br><span class="line">数据二  : 美国 | 加利福尼亚州洛杉矶IT7网络</span><br><span class="line"></span><br><span class="line">数据三  : 美国加利福尼亚</span><br><span class="line"></span><br><span class="line">URL     : http://www.cip.cc/104.xxx.xxx.xxx</span><br></pre></td></tr></table></figure><p>可以看到，IP 地址是国外的，证明我们已经可以科学上网了。</p><h2 id="启动-socat-转发"><a class="markdownIt-Anchor" href="#启动-socat-转发"></a> 启动 socat 转发</h2><p>照理说，实现科学上网之后，直接在 nginx 配置反向代理就能完成我们的任务。但遗憾的是，nginx 本身并不支持使用系统代理，也不像 <code>curl</code> 那样提供了 <code>--socks5-hostname</code> 参数用于显式指定代理，因此我们只能另谋出路。</p><p>这时我想到了 socat，socat 是一个多功能网络工具，它可以在两个网络数据流之间建立通道，实现端口转发的功能，同时还支持代理。但可惜的是，socat 目前只支持 socks4，还不支持 socks5，所以还不能直接用。在 GitHub 搜索发现已经有大佬给 socat 打过补丁，使其支持 socks5，因此决定使用这个补丁版本试试。这里给出项目的地址，有兴趣可以去点个 star：<a href="https://github.com/runsisi/socat">https://github.com/runsisi/socat</a></p><p>要安装这个补丁版的 socat，我们就不能直接使用 yum 或者 apt-get，而是要下载源码自己编译：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">apt-get install git curl autoconf yodl make</span><br><span class="line">git <span class="built_in">clone</span> --depth=1 https://github.com/runsisi/socat.git</span><br><span class="line"><span class="built_in">cd</span> socat</span><br><span class="line">autoconf</span><br><span class="line">./configure --prefix=/usr</span><br><span class="line">make</span><br><span class="line">make install</span><br></pre></td></tr></table></figure><blockquote><p>注意，上面的第一步首先安装了编译的过程中需要用到的其他工具，其中 yodl 在 CentOs 中可能无法使用 yum 安装，也可以考虑通过下载源码自行编译的方式解决，项目地址：<a href="https://gitlab.com/fbb-git/yodl">https://gitlab.com/fbb-git/yodl</a></p></blockquote><p>安装之后，启动 socat，监听 1081 端口，把流量转发给 Gravatar，当然，需要走代理：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">socat -d -d TCP4-LISTEN:1081,reuseaddr,fork SOCKS5:127.0.0.1:www.gravatar.com:443,socks5port=1080</span><br></pre></td></tr></table></figure><p>尝试访问一下 1081 端口，发现已经可以正常获取到头像数据了：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">$ curl -v -k -H &#x27;Host: www.gravatar.com&#x27; -o /dev/null https://127.0.0.1:1081/avatar/123</span><br><span class="line">* About to connect() to 127.0.0.1 port 1081 (#0)</span><br><span class="line">*   Trying 127.0.0.1...</span><br><span class="line">* Connected to 127.0.0.1 (127.0.0.1) port 1081 (#0)</span><br><span class="line">&gt; GET /avatar/123 HTTP/1.1</span><br><span class="line">&gt; User-Agent: curl/7.29.0</span><br><span class="line">&gt; Accept: */*</span><br><span class="line">&gt; Host: www.gravatar.com</span><br><span class="line">&gt;</span><br><span class="line">&lt; HTTP/1.1 200 OK</span><br><span class="line">&lt; Server: nginx</span><br><span class="line">&lt; Date: Thu, 22 Apr 2021 16:37:41 GMT</span><br><span class="line">&lt; Content-Type: image/jpeg</span><br><span class="line">&lt; Content-Length: 2637</span><br><span class="line">&lt; Connection: keep-alive</span><br><span class="line">&lt; Last-Modified: Wed, 11 Jan 1984 08:00:00 GMT</span><br><span class="line">&lt; Link: &lt;https://www.gravatar.com/avatar/123&gt;; rel=&quot;canonical&quot;</span><br><span class="line">&lt; Access-Control-Allow-Origin: *</span><br><span class="line">&lt; Content-Disposition: inline; filename=&quot;123.jpg&quot;</span><br><span class="line">&lt; Expires: Thu, 22 Apr 2021 16:42:41 GMT</span><br><span class="line">&lt; Cache-Control: max-age=300</span><br><span class="line">&lt; X-nc: HIT bur 3</span><br><span class="line">&lt; Accept-Ranges: bytes</span><br><span class="line">&lt;</span><br><span class="line">&#123; [data not shown]</span><br><span class="line">100  2637  100  2637    0     0   1172      0  0:00:02  0:00:02 --:--:--  1173</span><br><span class="line">* Connection #0 to host 127.0.0.1 left intact</span><br></pre></td></tr></table></figure><h2 id="配置-nginx-反向代理"><a class="markdownIt-Anchor" href="#配置-nginx-反向代理"></a> 配置 nginx 反向代理</h2><p>有了前面的准备工作之后，我们终于可以在 nginx 配置反向代理了，不过这里要注意的是，不能直接把流量转发给 Gravatar，而是转发给刚刚使用 socat 开启的本地 1081 端口。同时，为了减少回源的次数，提高访问速度，我们还可以做一层 <code>proxy_cache</code> 缓存。具体配置如下：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">http</span> &#123;</span><br><span class="line">  <span class="attribute">proxy_cache_path</span>  /var/cache/nginx levels=<span class="number">1</span>:<span class="number">2</span> keys_zone=gravatar:<span class="number">8m</span> max_size=<span class="number">10000m</span> inactive=<span class="number">600m</span>;</span><br><span class="line">  <span class="attribute">proxy_temp_path</span>   /var/cache/nginx/temp;</span><br><span class="line"></span><br><span class="line">  <span class="section">server</span> &#123;</span><br><span class="line">    <span class="attribute">listen</span>          <span class="number">443</span>;</span><br><span class="line">    <span class="attribute">server_name</span>     www.liuwj.me;</span><br><span class="line">    <span class="attribute">ssl</span>             <span class="literal">on</span>;</span><br><span class="line"></span><br><span class="line">    <span class="section">location</span> / &#123;</span><br><span class="line">      <span class="attribute">root</span>          /var/www/www.liuwj.me;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="section">location</span> /gravatar/ &#123;</span><br><span class="line">      <span class="attribute">proxy_pass</span>         https://localhost:1081/avatar/;</span><br><span class="line">      <span class="attribute">proxy_set_header</span>   Host www.gravatar.com;</span><br><span class="line">      <span class="attribute">proxy_cache</span>        gravatar;</span><br><span class="line">      <span class="attribute">proxy_cache_valid</span>  <span class="number">200</span> <span class="number">301</span> <span class="number">302</span> <span class="number">7d</span>;</span><br><span class="line">      <span class="attribute">proxy_cache_valid</span>  <span class="number">404</span> <span class="number">502</span> <span class="number">1m</span>;</span><br><span class="line">      <span class="attribute">expires</span>            <span class="number">7d</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>配置完成后，尝试使用浏览器打开一个头像链接，确认是否能正常访问：<a href="https://www.liuwj.me/gravatar/123">https://www.liuwj.me/gravatar/123</a></p><p>大功告成！！一个头像请求，从浏览器发出之后，经过的路径应该是这样子的：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">+---------+      +-------+      +-------+      +---------+      +----------+      +----------+</span><br><span class="line">| browser | ---&gt; | nginx | ---&gt; | socat | ---&gt; | sslocal | ---&gt; | ssserver | ---&gt; | gravatar |</span><br><span class="line">+---------+      +-------+      +-------+      +---------+      +----------+      +----------+</span><br></pre></td></tr></table></figure><p>可以说是十分曲折了…</p>]]></content>
    
    
    <summary type="html">&lt;blockquote class=&quot;blockquote-center&quot;&gt;生命不息，折腾不止&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://cn.gravatar.com/&quot;&gt;Gravatar&lt;/a&gt; 即全球通用头像 (Globally Recognized Avatar) 服务，用户只要在上面上传了自己的头像，那么在所有支持的网站上发帖时，只要提供与这个头像关联的 Email，就可以显示出自己的 Gravatar 头像。可以说是「一次上传，全网通用」~~&lt;/p&gt;
&lt;p&gt;可惜国内的网络环境实在一言难尽，Gravatar 常年都处于无法访问的状态，所以本站一直都是用 v2ex 提供的 CDN 镜像，然而，就在前两周，v2ex 也被墙了，emm… 因为不想再白嫖其他的国内镜像，因此开始考虑自己动手搭建。这个事情其实挺简单的，网上随便搜索一下就有答案，只要有一台墙外的 VPS，用 nginx 给 Gravatar 做个反向代理就好。&lt;/p&gt;
&lt;p&gt;但是，如果你的主机在墙内呢，怎么办？&lt;/p&gt;</summary>
    
    
    
    
    <category term="教程" scheme="https://www.liuwj.me/tags/%E6%95%99%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>为 Ktorm 框架拓展 PostgreSQL 方言进行 Json 访问</title>
    <link href="https://www.liuwj.me/posts/ktorm-dialect-extension/"/>
    <id>https://www.liuwj.me/posts/ktorm-dialect-extension/</id>
    <published>2020-10-14T00:00:00.000Z</published>
    <updated>2026-03-01T02:56:55.951Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>本文转自 <a href="https://skyblond.info/archives/751.html">https://skyblond.info/archives/751.html</a></p></blockquote><p>最近从滴滴辞职，为期 5 天的暑假正式开始了，寻思着做一点有意义的事情提升一下自己。遂决定自己写一套专门用于复杂查询的通联日志管理系统，数据库选用了 PostgreSQL，该数据库可以直接对 Json 类型的数据进行高级查询，然而 Ktorm 框架并不支持此功能，因此本文将记述为该框架进行拓展的过程。</p><span id="more"></span><h2 id="需求"><a class="markdownIt-Anchor" href="#需求"></a> 需求</h2><p>假设数据库中有一表 <code>qso_infos</code> 用于存储通联日志，其中 <code>qsl_info</code> 字段表示 QSL 相关事宜的记录，该字段为 <code>jsonb</code> 类型，样例如下：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;lotw&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;uploaded&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;uploadDate&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2020-06-20&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;comfirmed&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;comfireDate&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2020-07-21&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;card&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;sent&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;sent&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;required&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;sentDate&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;requiredDate&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2020-06-21&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;via&quot;</span><span class="punctuation">:</span> <span class="string">&quot;bureau&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;received&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;received&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;required&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;receivedDate&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;requiredDate&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2020-06-22&quot;</span><span class="punctuation">,</span></span><br><span class="line">            <span class="attr">&quot;via&quot;</span><span class="punctuation">:</span> <span class="string">&quot;direct&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;comment&quot;</span><span class="punctuation">:</span> <span class="string">&quot;因为COVID-19推迟寄送QSL卡片&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>现在需要查询所有 LoTW 未上传的记录，应当如何利用 Ktorm 在数据库端完成？</p><p>如果需要查询所有 comment 为空的记录，应当如何利用 Ktorm 在数据库端完成？</p><h2 id="分析"><a class="markdownIt-Anchor" href="#分析"></a> 分析</h2><p>如果直接编写 SQL 的话，应当按如下编写：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> qso_infos <span class="keyword">WHERE</span> qsl_info<span class="operator">-</span><span class="operator">&gt;</span><span class="string">&#x27;lotw&#x27;</span><span class="operator">-</span><span class="operator">&gt;&gt;</span><span class="string">&#x27;uploaded&#x27;</span> <span class="operator">=</span> <span class="string">&#x27;false&#x27;</span>;</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> qso_infos <span class="keyword">WHERE</span> qsl_info<span class="operator">-</span><span class="operator">&gt;&gt;</span><span class="string">&#x27;comment&#x27;</span> <span class="operator">=</span> <span class="string">&#x27;&#x27;</span>;</span><br></pre></td></tr></table></figure><p>其中第一句还可以这样写：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> qso_infos <span class="keyword">WHERE</span> (qsl_info<span class="operator">-</span><span class="operator">&gt;&gt;</span><span class="string">&#x27;lotw&#x27;</span>)::json<span class="operator">-</span><span class="operator">&gt;&gt;</span><span class="string">&#x27;uploaded&#x27;</span> <span class="operator">=</span> <span class="string">&#x27;false&#x27;</span>;</span><br></pre></td></tr></table></figure><p>第一种写法用到了两个不同的运算符：<code>-&gt;</code> 是作为 Json 取出，而 <code>-&gt;&gt;</code> 则是作为字符串取出。需要注意的是对于 Json Object 这两个运算符是根据输入的字符串作为键去取值，而对于 Json Array 则是按照输入的整形作为从 0 开始的索引去取值。为了最大兼容性的考虑，本文将同时实现每个运算符的两个形式。</p><p>对于第二种写法，虽然只需要自定义一个运算符即可，但还需要将转换为 Json 作为一个额外的运算符实现。本文也将实现该运算符。</p><h2 id="实现"><a class="markdownIt-Anchor" href="#实现"></a> 实现</h2><h3 id="sql-类型扩展"><a class="markdownIt-Anchor" href="#sql-类型扩展"></a> SQL 类型扩展</h3><p>为了实现对 Json 的访问，首先应当定义 <code>json</code> 和 <code>jsonb</code> 两个 SQL 数据类型。这里我没有使用 ktorm 的模块，而是基于 Gson 写了一个：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> info.skyblond.jinn.extension</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.google.gson.Gson</span><br><span class="line"><span class="keyword">import</span> org.ktorm.schema.BaseTable</span><br><span class="line"><span class="keyword">import</span> org.ktorm.schema.Column</span><br><span class="line"><span class="keyword">import</span> org.ktorm.schema.SqlType</span><br><span class="line"><span class="keyword">import</span> org.ktorm.schema.TypeReference</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Type</span><br><span class="line"><span class="keyword">import</span> java.sql.PreparedStatement</span><br><span class="line"><span class="keyword">import</span> java.sql.ResultSet</span><br><span class="line"><span class="keyword">import</span> java.sql.Types</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">JsonbSqlType</span>&lt;<span class="type">T : Any</span>&gt;(<span class="keyword">private</span> <span class="keyword">val</span> type: Type) : SqlType&lt;T&gt;(Types.JAVA_OBJECT, typeName = <span class="string">&quot;jsonb&quot;</span>) &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">val</span> gson = Gson()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">doSetParameter</span><span class="params">(ps: <span class="type">PreparedStatement</span>, index: <span class="type">Int</span>, parameter: <span class="type">T</span>)</span></span> &#123;</span><br><span class="line">        ps.setString(index, gson.toJson(parameter))</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">doGetResult</span><span class="params">(rs: <span class="type">ResultSet</span>, index: <span class="type">Int</span>)</span></span>: T? &#123;</span><br><span class="line">        <span class="keyword">val</span> json = rs.getString(index)</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">if</span> (json.isNullOrBlank()) &#123;</span><br><span class="line">            <span class="literal">null</span></span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            gson.fromJson(json, type)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="type">&lt;C : Any&gt;</span> BaseTable<span class="type">&lt;*&gt;</span>.<span class="title">jsonb</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">    name: <span class="type">String</span>,</span></span></span><br><span class="line"><span class="params"><span class="function">    typeReference: <span class="type">TypeReference</span>&lt;<span class="type">C</span>&gt;</span></span></span><br><span class="line"><span class="params"><span class="function">)</span></span>: Column&lt;C&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> registerColumn(name, JsonbSqlType(typeReference.referencedType))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title class_">JsonSqlType</span>&lt;<span class="type">T : Any</span>&gt;(<span class="keyword">private</span> <span class="keyword">val</span> type: Type) : SqlType&lt;T&gt;(Types.JAVA_OBJECT, typeName = <span class="string">&quot;json&quot;</span>) &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">val</span> gson = Gson()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">doSetParameter</span><span class="params">(ps: <span class="type">PreparedStatement</span>, index: <span class="type">Int</span>, parameter: <span class="type">T</span>)</span></span> &#123;</span><br><span class="line">        ps.setString(index, gson.toJson(parameter))</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">doGetResult</span><span class="params">(rs: <span class="type">ResultSet</span>, index: <span class="type">Int</span>)</span></span>: T? &#123;</span><br><span class="line">        <span class="keyword">val</span> json = rs.getString(index)</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">if</span> (json.isNullOrBlank()) &#123;</span><br><span class="line">            <span class="literal">null</span></span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            gson.fromJson(json, type)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="type">&lt;C : Any&gt;</span> BaseTable<span class="type">&lt;*&gt;</span>.<span class="title">json</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">    name: <span class="type">String</span>,</span></span></span><br><span class="line"><span class="params"><span class="function">    typeReference: <span class="type">TypeReference</span>&lt;<span class="type">C</span>&gt;</span></span></span><br><span class="line"><span class="params"><span class="function">)</span></span>: Column&lt;C&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> registerColumn(name, JsonSqlType(typeReference.referencedType))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>关于代码就不多说了，具体可以参考 <a href="https://www.ktorm.org/zh-cn/schema-definition.html#%E6%89%A9%E5%B1%95%E6%9B%B4%E5%A4%9A%E7%9A%84%E7%B1%BB%E5%9E%8B">Ktorm 文档 - 定义表结构 - 扩展更多的类型</a>。这里最重要的一点就是要使用限制比较宽松的框架进行 Json 转换，一开始我试图使用 kotlinx 的 serialization 进行，于是进行 Json 转换的时候就需要一个 <code>KSerializer</code> 对象才能工作，而该对象的获取渠道是 <code>SomeClass.serializer()</code>，其中 <code>SomeClass</code> 需要被 <code>@kotlinx.serialization.Serializable</code> 注解。之后进行 Json 操作时为了最大的兼容性，通常都是认为操作的结果是 <code>Any</code> 而非特定一个类，那么问题就来了：<code>Any</code> 类似 Java 的 <code>Object</code>，是万物之父，而所有被 <code>@Serializable</code> 注解的类，可没有一个统一的父类。因此这样的架构在后续实现运算符的时候就会非常难受。最起码也是要实现了 Java 的 <code>Serializable</code> 接口（或其他框架的统一的接口）。</p><p>现在有了 Json 数据类型，接下来我们就可以实现运算符了。</p><h3 id="自定义运算符"><a class="markdownIt-Anchor" href="#自定义运算符"></a> 自定义运算符</h3><p>关于自定义运算符，这里同样不多赘述，详细指导可以参考 <a href="https://www.ktorm.org/zh-cn/operators.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E8%BF%90%E7%AE%97%E7%AC%A6">Ktorm 文档 - 运算符 - 自定义运算符</a>。拓展的代码如下：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> info.skyblond.jinn.extension</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.ktorm.expression.ArgumentExpression</span><br><span class="line"><span class="keyword">import</span> org.ktorm.expression.ScalarExpression</span><br><span class="line"><span class="keyword">import</span> org.ktorm.schema.ColumnDeclaring</span><br><span class="line"><span class="keyword">import</span> org.ktorm.schema.IntSqlType</span><br><span class="line"><span class="keyword">import</span> org.ktorm.schema.SqlType</span><br><span class="line"><span class="keyword">import</span> org.ktorm.schema.VarcharSqlType</span><br><span class="line"></span><br><span class="line"><span class="keyword">data</span> <span class="keyword">class</span> <span class="title class_">AsJsonExpression</span>(</span><br><span class="line">    <span class="keyword">val</span> left: ScalarExpression&lt;*&gt;,</span><br><span class="line">    <span class="keyword">override</span> <span class="keyword">val</span> sqlType: SqlType&lt;Any&gt; = JsonSqlType(Any::<span class="keyword">class</span>.java),</span><br><span class="line">    <span class="keyword">override</span> <span class="keyword">val</span> isLeafNode: <span class="built_in">Boolean</span> = <span class="literal">false</span>,</span><br><span class="line">    <span class="keyword">override</span> <span class="keyword">val</span> extraProperties: Map&lt;String, Any&gt; = mapOf(),</span><br><span class="line">    <span class="keyword">val</span> alreadyJson: <span class="built_in">Boolean</span> = <span class="literal">false</span></span><br><span class="line">) : ScalarExpression&lt;Any&gt;()</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * This function make a AsJsonExpression.</span></span><br><span class="line"><span class="comment"> * When alreadyJson is true, the expression does nothing, just to make compiler happy.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">fun</span> ColumnDeclaring<span class="type">&lt;*&gt;</span>.<span class="title">asJson</span><span class="params">(alreadyJson: <span class="type">Boolean</span> = <span class="literal">false</span>)</span></span>: AsJsonExpression &#123;</span><br><span class="line">    <span class="keyword">return</span> AsJsonExpression(asExpression(), alreadyJson = alreadyJson)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Get as Json</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">data</span> <span class="keyword">class</span> <span class="title class_">JsonAccessExpression</span>&lt;<span class="type">T : Any</span>&gt;(</span><br><span class="line">    <span class="keyword">val</span> left: AsJsonExpression,</span><br><span class="line">    <span class="keyword">val</span> right: ArgumentExpression&lt;T&gt;,</span><br><span class="line">    <span class="keyword">override</span> <span class="keyword">val</span> sqlType: SqlType&lt;Any&gt; = JsonSqlType(Any::<span class="keyword">class</span>.java),</span><br><span class="line">    <span class="keyword">override</span> <span class="keyword">val</span> isLeafNode: <span class="built_in">Boolean</span> = <span class="literal">false</span>,</span><br><span class="line">    <span class="keyword">override</span> <span class="keyword">val</span> extraProperties: Map&lt;String, Any&gt; = mapOf()</span><br><span class="line">) : ScalarExpression&lt;Any&gt;()</span><br><span class="line"></span><br><span class="line"><span class="keyword">operator</span> <span class="function"><span class="keyword">fun</span> AsJsonExpression.<span class="title">get</span><span class="params">(param: <span class="type">String</span>)</span></span>: AsJsonExpression &#123;</span><br><span class="line">    <span class="keyword">return</span> JsonAccessExpression(<span class="keyword">this</span>, ArgumentExpression(param, VarcharSqlType)).asJson(<span class="literal">true</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">operator</span> <span class="function"><span class="keyword">fun</span> AsJsonExpression.<span class="title">get</span><span class="params">(param: <span class="type">Int</span>)</span></span>: AsJsonExpression &#123;</span><br><span class="line">    <span class="keyword">return</span> JsonAccessExpression(<span class="keyword">this</span>, ArgumentExpression(param, IntSqlType)).asJson(<span class="literal">true</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Get as Text</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">data</span> <span class="keyword">class</span> <span class="title class_">JsonAccessAsTextExpression</span>&lt;<span class="type">T : Any</span>&gt;(</span><br><span class="line">    <span class="keyword">val</span> left: AsJsonExpression,</span><br><span class="line">    <span class="keyword">val</span> right: ArgumentExpression&lt;T&gt;,</span><br><span class="line">    <span class="keyword">override</span> <span class="keyword">val</span> sqlType: SqlType&lt;String&gt; = VarcharSqlType,</span><br><span class="line">    <span class="keyword">override</span> <span class="keyword">val</span> isLeafNode: <span class="built_in">Boolean</span> = <span class="literal">false</span>,</span><br><span class="line">    <span class="keyword">override</span> <span class="keyword">val</span> extraProperties: Map&lt;String, Any&gt; = mapOf()</span><br><span class="line">) : ScalarExpression&lt;String&gt;()</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> AsJsonExpression.<span class="title">getAsString</span><span class="params">(param: <span class="type">String</span>)</span></span>: JsonAccessAsTextExpression&lt;String&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> JsonAccessAsTextExpression(<span class="keyword">this</span>, ArgumentExpression(param, VarcharSqlType))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> AsJsonExpression.<span class="title">getAsString</span><span class="params">(param: <span class="type">Int</span>)</span></span>: JsonAccessAsTextExpression&lt;<span class="built_in">Int</span>&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> JsonAccessAsTextExpression(<span class="keyword">this</span>, ArgumentExpression(param, IntSqlType))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在这里我们实现了 <code>AsJsonExpression</code>，该表达式将前面的语句转换成 Json 类型；<code>JsonAccessExpression</code>，该表达式将对 Json 类型的数据进行 <code>-&gt;</code> 运算；<code>JsonAccessAsTextExpression</code>，该表达式将对 Json 类型的数据进行 <code>-&gt;&gt;</code> 运算。</p><p>在 Ktorm 中对于数据的面向对象式筛选，实际上是基于面向对象的写法产生一个表达式树。该树中的每一个 <code>Expression</code> 表示一个运算或参数，最终将被解析为一条 SQL 语句。</p><p>值得注意的是在 <code>AsJsonExpression</code> 的实现中只有 <code>left</code>，原本是打算将其作为 <code>AsDataTypeExpression</code>，然后 <code>right</code> 作为一个 <code>SqlType</code> 来实现更通用的功能的，但是这样一来后面的实现将无法保证只对 Json 类型的语句进行访问：<code>JsonSqlType</code> 要能够适配所有情况，其类型必定是 <code>SqlType</code>，这样一来将无法区分哪些表达式是 Json，哪些不是。当然你也可以额外在加一个类型：<code>AsDataTypeExpression</code>，T 作为 <code>SqlType</code>，而 U 直接存储 <code>JsonSqlType</code>，但是就本文而言，还是单独搞一个 <code>AsJsonExpression</code> 来的最实在。</p><p>关于 <code>asJson()</code> 函数，设置 <code>alreadyJson</code> 的目的就是后面对于 <code>-&gt;</code> 运算符，其运算结果本身就是 Json，而对于编译器来说则是 <code>JsonAccessExpression</code> 类型，不能应用 <code>AsJsonExpression</code> 的访问操作。因此这里只是单纯的为了让编译器开心，当进行 <code>-&gt;</code> 访问时，除了产生一个访问表达式之外，还在外面报一个 <code>AsJsonExpression</code>，这样对于编译器来说就是合法的 Json 类型了，而在生成 SQL 语句时通过判断 <code>alreadyJson</code> 字段可以跳过本次类型转换。</p><p>最后为了写起来更舒爽，对于作为 Json Object 取出的 <code>-&gt;</code> 运算，我重载了 kotlin 内置的 get 方法，这样就可以通过 <code>asJson()[keyName]</code> 的形式进行访问了。</p><p>而只进行到此还是不够全面，目前 ktorm 还不能正确翻译这些表达式。下一步将进行方言扩展。</p><h3 id="扩展方言"><a class="markdownIt-Anchor" href="#扩展方言"></a> 扩展方言</h3><p>既然说扩展方言，就是说我们并不像替代原有的 PostgreSQL 方言，而 Ktorm 的作者也贴心的将方言实现类加了 open 关键字，这样我们就可以自由的进行扩展了。根据 <a href="https://www.ktorm.org/zh-cn/operators.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E8%BF%90%E7%AE%97%E7%AC%A6">Ktorm 文档 - 运算符 - 自定义运算符</a>，扩展的方言应当覆盖对未知表达式的处理：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> info.skyblond.jinn.extension</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.ktorm.database.Database</span><br><span class="line"><span class="keyword">import</span> org.ktorm.expression.SqlExpression</span><br><span class="line"><span class="keyword">import</span> org.ktorm.expression.SqlFormatter</span><br><span class="line"><span class="keyword">import</span> org.ktorm.support.postgresql.PostgreSqlDialect</span><br><span class="line"><span class="keyword">import</span> org.ktorm.support.postgresql.PostgreSqlFormatter</span><br><span class="line"></span><br><span class="line"><span class="keyword">open</span> <span class="keyword">class</span> <span class="title class_">MyPostgreSqlDialect</span> : <span class="type">PostgreSqlDialect</span>() &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">createSqlFormatter</span><span class="params">(database: <span class="type">Database</span>, beautifySql: <span class="type">Boolean</span>, indentSize: <span class="type">Int</span>)</span></span>: SqlFormatter &#123;</span><br><span class="line">        <span class="keyword">return</span> MyPostgreSqlFormatter(database, beautifySql, indentSize)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MyPostgreSqlFormatter</span>(database: Database, beautifySql: <span class="built_in">Boolean</span>, indentSize: <span class="built_in">Int</span>)</span><br><span class="line">    : PostgreSqlFormatter(database, beautifySql, indentSize) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">visitUnknown</span><span class="params">(expr: <span class="type">SqlExpression</span>)</span></span>: SqlExpression &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">when</span> (expr) &#123;</span><br><span class="line">            <span class="keyword">is</span> AsJsonExpression -&gt; &#123;</span><br><span class="line">                <span class="keyword">if</span> (expr.left.removeBrackets) &#123;</span><br><span class="line">                    visit(expr.left)</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    write(<span class="string">&quot;(&quot;</span>)</span><br><span class="line">                    visit(expr.left)</span><br><span class="line">                    removeLastBlank()</span><br><span class="line">                    write(<span class="string">&quot;) &quot;</span>)</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="comment">// if already json, then do nothing, just make compiler happy</span></span><br><span class="line">                <span class="keyword">if</span> (!expr.alreadyJson) &#123;</span><br><span class="line">                    removeLastBlank()</span><br><span class="line">                    write(<span class="string">&quot;::json &quot;</span>)</span><br><span class="line">                &#125;</span><br><span class="line">                expr</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">is</span> JsonAccessAsTextExpression&lt;*&gt; -&gt; &#123;</span><br><span class="line">                <span class="comment">// Json only, no need to add brackets</span></span><br><span class="line">                visit(expr.left)</span><br><span class="line"></span><br><span class="line">                write(<span class="string">&quot;-&gt;&gt; &quot;</span>)</span><br><span class="line"></span><br><span class="line">                visit(expr.right)</span><br><span class="line"></span><br><span class="line">                expr</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">is</span> JsonAccessExpression&lt;*&gt; -&gt; &#123;</span><br><span class="line">                <span class="comment">// Json only, no need to add brackets</span></span><br><span class="line">                visit(expr.left)</span><br><span class="line"></span><br><span class="line">                write(<span class="string">&quot;-&gt; &quot;</span>)</span><br><span class="line"></span><br><span class="line">                visit(expr.right)</span><br><span class="line"></span><br><span class="line">                expr</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">else</span> -&gt; &#123;</span><br><span class="line">                <span class="keyword">super</span>.visitUnknown(expr)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在遇到没有见过的表达式时，将回调父类的处理函数，而这个函数的默认行为就是抛异常。这里在产生 SQL 语句的时候有一些细节要注意。</p><p>对于转换为 Json 的语句，需要时应当增加括号来保证运算优先级的正确性。而对于 Json 的访问，其左值一定是 Json，而右值则一定是数字或字符串类型的参数，因此左右皆无需增加括号。</p><p>最后我们需要在连接数据库的时候指定该方言。</p><h3 id="结果"><a class="markdownIt-Anchor" href="#结果"></a> 结果</h3><p>首先连接数据库：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> pgDataSource = PGDataSource()</span><br><span class="line">pgDataSource.serverName = <span class="string">&quot;localhost&quot;</span></span><br><span class="line">pgDataSource.portNumber = <span class="number">5432</span></span><br><span class="line">pgDataSource.user = <span class="string">&quot;logbook&quot;</span></span><br><span class="line">pgDataSource.password = <span class="string">&quot;***&quot;</span></span><br><span class="line">pgDataSource.databaseName = <span class="string">&quot;logbook&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">val</span> config = HikariConfig()</span><br><span class="line">config.driverClassName = <span class="string">&quot;com.impossibl.postgres.jdbc.PGDataSource&quot;</span></span><br><span class="line">config.dataSource = pgDataSource</span><br><span class="line">config.addDataSourceProperty(<span class="string">&quot;cachePrepStmts&quot;</span>, <span class="string">&quot;true&quot;</span>)</span><br><span class="line">config.addDataSourceProperty(<span class="string">&quot;prepStmtCacheSize&quot;</span>, <span class="string">&quot;250&quot;</span>)</span><br><span class="line">config.addDataSourceProperty(<span class="string">&quot;prepStmtCacheSqlLimit&quot;</span>, <span class="string">&quot;2048&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">val</span> hikariDataSource = HikariDataSource(config)</span><br><span class="line"></span><br><span class="line"><span class="keyword">val</span> database = Database.connect(hikariDataSource, dialect = MyPostgreSqlDialect())</span><br></pre></td></tr></table></figure><p>注意最后在连接数据库的时候指定了我们自己实现的方言。之后在定义完表和实体类之后进行查询：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">database.sequenceOf(QsoInfos).filter &#123;</span><br><span class="line">    it.qslInfo.asJson(<span class="literal">true</span>)[<span class="string">&quot;lotw&quot;</span>].getAsString(<span class="string">&quot;uploaded&quot;</span>) eq <span class="string">&quot;true&quot;</span></span><br><span class="line">&#125;.forEach &#123;</span><br><span class="line">    println(it)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">database.sequenceOf(QsoInfos).filter &#123;</span><br><span class="line">    it.qslInfo.asJson(<span class="literal">true</span>)[<span class="string">&quot;card&quot;</span>][<span class="string">&quot;sent&quot;</span>].getAsString(<span class="string">&quot;sent&quot;</span>) eq <span class="string">&quot;false&quot;</span></span><br><span class="line">&#125;.forEach &#123;</span><br><span class="line">    println(it)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">database.sequenceOf(QsoInfos).filter &#123;</span><br><span class="line">    it.qslInfo.asJson(<span class="literal">true</span>).getAsString(<span class="string">&quot;comment&quot;</span>) notEq <span class="string">&quot;&quot;</span></span><br><span class="line">&#125;.forEach &#123;</span><br><span class="line">    println(it)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">database.sequenceOf(QsoInfos).filter &#123;</span><br><span class="line">    it.qslInfo.asJson(<span class="literal">true</span>).getAsString(<span class="string">&quot;card&quot;</span>).asJson().getAsString(<span class="string">&quot;sent&quot;</span>).asJson().getAsString(<span class="string">&quot;sent&quot;</span>) notEq <span class="string">&quot;true&quot;</span></span><br><span class="line">&#125;.forEach &#123;</span><br><span class="line">    println(it)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应的分别产生了如下 SQL：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">DEBUG org.ktorm.database - SQL: select ... from qso_infos where ((qso_infos.qsl_info -&gt; ?) -&gt;&gt; ?) = ? </span><br><span class="line">DEBUG org.ktorm.database - Parameters: [lotw(varchar), uploaded(varchar), true(varchar)]</span><br><span class="line"></span><br><span class="line">DEBUG org.ktorm.database - SQL: select ... from qso_infos where (((qso_infos.qsl_info -&gt; ?) -&gt; ?) -&gt;&gt; ?) = ? </span><br><span class="line">DEBUG org.ktorm.database - Parameters: [card(varchar), sent(varchar), sent(varchar), false(varchar)]</span><br><span class="line"></span><br><span class="line">DEBUG org.ktorm.database - SQL: select ... from qso_infos where (qso_infos.qsl_info -&gt;&gt; ?) &lt;&gt; ? </span><br><span class="line">DEBUG org.ktorm.database - Parameters: [comment(varchar), (varchar)]</span><br><span class="line"></span><br><span class="line">DEBUG org.ktorm.database - SQL: select ... from qso_infos where (((qso_infos.qsl_info -&gt;&gt; ?)::json -&gt;&gt; ?)::json -&gt;&gt; ?) &lt;&gt; ? </span><br><span class="line">DEBUG org.ktorm.database - Parameters: [card(varchar), sent(varchar), sent(varchar), true(varchar)]</span><br></pre></td></tr></table></figure><p>看起来符合预期，程序也没有报错。至此可以算是完美决绝问题。</p><p>- 全文完 -</p>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;本文转自 &lt;a href=&quot;https://skyblond.info/archives/751.html&quot;&gt;https://skyblond.info/archives/751.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;最近从滴滴辞职，为期 5 天的暑假正式开始了，寻思着做一点有意义的事情提升一下自己。遂决定自己写一套专门用于复杂查询的通联日志管理系统，数据库选用了 PostgreSQL，该数据库可以直接对 Json 类型的数据进行高级查询，然而 Ktorm 框架并不支持此功能，因此本文将记述为该框架进行拓展的过程。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Kotlin" scheme="https://www.liuwj.me/tags/Kotlin/"/>
    
    <category term="Ktorm" scheme="https://www.liuwj.me/tags/Ktorm/"/>
    
  </entry>
  
  <entry>
    <title>谈谈 Java 代码的兼容性</title>
    <link href="https://www.liuwj.me/posts/talk-about-the-compatibility-of-java-code/"/>
    <id>https://www.liuwj.me/posts/talk-about-the-compatibility-of-java-code/</id>
    <published>2019-10-20T00:00:00.000Z</published>
    <updated>2026-03-01T02:56:55.951Z</updated>
    
    <content type="html"><![CDATA[<p>最近踩了个坑，事情的经过是这样，我在做一个需求，要在某个实体类中加个字段，这个类的名字是 <code>Banner</code>。</p><p>但是当我打开这个类的时候，看到的除了字段定义以外还有一大堆使用 idea 生成的 getter/setter 方法。甚至这些 getter/setter 方法占用的代码行数反而更多，严重干扰视线，阅读代码体验极差。</p><p>这时我就产生了重构的想法，思路是删掉这些没必要的 getter/setter 方法，改用 lombok 的 <code>@Data</code> 注解代替。因为 lombok 本来在项目中就有使用，所以应该不会有什么问题。改完之后，我测试了我正在做的这个功能，一切正常，代码部署到测试环境之后也运行良好。</p><p>但是万万没有想到，问题竟然出现在与这个功能看起来毫不相关的另一个模块。这个模块启动后抛出了一个 <code>NoSuchMethodError</code>：</p><span id="more"></span><p><img src="https://www.liuwj.me/files/in-post/java-code-compatibility-01.png" alt="img" /></p><p>抛异常的地方确实是我改过的 <code>Banner</code> 类，但是 lombok 应该会为我们生成相应的 getter/setter 方法，所以这里不应该找不到才对，难道是 lombok 抽风了？</p><p>查找原因的时候，有的同事认为原因是我把 lombok 的依赖设置成 optional，导致运行的时候没有 lombok 的 jar 才出现这个异常。然而这种理解是错误的，因为 lombok 生成代码的原理是通过 javac 提供的 APT（Annotation Processing Tool，注解处理器）机制在编译过程中对 Java 代码的 AST 进行修改，这一切都发生在编译时，因此在运行时并不需要 lombok 的存在。换句话说，如果是因为 lombok 导致的问题，不会等到运行的时候才抛出异常，而是在编译的时候就崩了。</p><p>真正的原因比较隐蔽，仔细寻找之后才能发现。在我修改前的 <code>Banner</code> 类中，有一个 <code>id</code> 字段，它的定义是这样的：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> Long id;</span><br></pre></td></tr></table></figure><p>可以看到，这里使用的是包装类型的 <code>Long</code>，但是它的 getter/setter 方法使用的却是基本类型的 <code>long</code>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">long</span> <span class="title function_">getId</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> id;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setId</span><span class="params">(<span class="type">long</span> id)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.id = id;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>严格来说，这样根本不符合 Java Bean 的规范，使用 idea 也不可能会生成这样的 getter/setter 方法，所以我猜测原代码的作者应该是先使用 idea 生成了代码，然后手动修改了里面的类型。</p><p>当我把这两个方法删掉，加上 lombok 的 <code>@Data</code> 注解之后，lombok 给我们生成的的 getter/setter 方法的类型会与字段的类型相同，即 <code>Long getId()</code>。</p><p>理论上，当方法的签名从 <code>long getId()</code> 变成 <code>Long getId()</code> 之后，代码是不会报错的，因为就算原来有地方使用了 <code>long</code> 来接收返回值，我们的方法签名改成 <code>Long</code> 之后返回的包装类型也会被自动拆箱。然而正因为它不会报错，才让我没有立即发现问题。</p><p>当我们讨论一段被修改的代码的兼容性的时候，我们其实隐含了两层完全不一样的意思。<strong>兼容性分为两个层次：</strong></p><ul><li><strong>源码级兼容：当我们修改了一段代码，依赖它的其他代码在编译时不需要修改即可直接通过，此为源码级兼容。</strong></li><li><strong>二进制级兼容：比源码级兼容更进一层，当我们修改了一段代码，依赖它的其他代码不需要修改，甚至也不需要重新编译也可运行正常。</strong></li></ul><p>一般来说，我们平时写代码只需要做到源码级兼容即可，二进制级兼容只在很少情况下才会需要。</p><p>在这个例子中，我们的方法签名在无意间从 <code>long getId()</code> 变成了 <code>Long getId()</code>，这在源码层面是兼容的，所以编译的时候不会报错。但是在 Java 字节码中，<code>long getId()</code> 和 <code>Long getId()</code> 是两个完全不一样的方法，因此这个修改是二进制不兼容的。</p><p>我的项目的模块依赖是这样的：</p><img src="https://www.liuwj.me/files/in-post/java-code-compatibility-02.png" style="max-width: 50%"/><p><code>Banner.java</code> 在模块 C 中，我修改之后 deploy 了一个新版本到 maven 仓库，因此其他模块可以下载到它。</p><p>模块 B 依赖了模块 C，并且在里面使用了 <code>Banner</code> 类的 <code>long getId()</code> 方法，这正是发生这次错误的原因。</p><p>模块 A 同时依赖了模块 B 和模块 C。因为我修改过模块 C，并且 deploy 了一个新版本，因此在构建的时候会下载这个最新的 jar 包，但是我并没有修改过模块 B，所以模块 A 在构建的时候使用的仍然是旧的 jar 包。这个旧的 jar 包在运行的时候会尝试去调用签名为 <code>long getId()</code> 的方法，但是这个方法的签名已经被我在无意间改成了 <code>Long getId()</code>，因此才会发生找不到方法的异常。</p><p>找到异常的原因之后，解决方法很自然就有了，那就是重新编译模块 B，并把它 deploy 到 maven 仓库中即可。</p><p>这次的问题是一个说明代码兼容性的不同层次的一个很好的例子，这种问题排查起来虽然不算太难，但是发生的原因十分隐蔽，也足够我们折腾一会。为了避免大家以后踩到和我类似的坑，在这里我把整个过程记录下来，然后给出一点不成熟的小建议：</p><ul><li><del>面向甩锅编程，如非必要，坚决不要修改除自己需求以外的任何一行代码，更不要幻想重构，否则出了事情可能会背锅<i class="emoji emoji-joy"></i></del></li><li>字段的 getter/setter 方法要符合 Java Bean 的通用规范，否则其他同学在阅读或修改代码的时候容易忽略掉一些细微之处，某些框架也可能会因为这个产生一些奇怪的 bug。</li><li>为保证 getter/setter 方法能严格符合规范，推荐尽量使用 lombok，不推荐使用 idea 的代码生成，多出来的代码会多出额外的维护成本。</li><li>就算使用 idea 来生成 getter/setter 方法，在生成后也请尽量不要去编辑，若以后字段的名字或类型有变化，可把原来的 getter/setter 方法删掉再重新生成一次。</li><li>如果这些模块属于同一个工程，建议使用 maven 父子工程来组织项目，这样在编译模块 A 的时候，也会同时编译它依赖的模块 B 和模块 C，可以很大程度避免此类问题。</li></ul><p><strong>当然，最好的方法还是赶紧换成 Kotlin (强行安利)，去 tm 的 getter/setter…</strong></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近踩了个坑，事情的经过是这样，我在做一个需求，要在某个实体类中加个字段，这个类的名字是 &lt;code&gt;Banner&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;但是当我打开这个类的时候，看到的除了字段定义以外还有一大堆使用 idea 生成的 getter/setter 方法。甚至这些 getter/setter 方法占用的代码行数反而更多，严重干扰视线，阅读代码体验极差。&lt;/p&gt;
&lt;p&gt;这时我就产生了重构的想法，思路是删掉这些没必要的 getter/setter 方法，改用 lombok 的 &lt;code&gt;@Data&lt;/code&gt; 注解代替。因为 lombok 本来在项目中就有使用，所以应该不会有什么问题。改完之后，我测试了我正在做的这个功能，一切正常，代码部署到测试环境之后也运行良好。&lt;/p&gt;
&lt;p&gt;但是万万没有想到，问题竟然出现在与这个功能看起来毫不相关的另一个模块。这个模块启动后抛出了一个 &lt;code&gt;NoSuchMethodError&lt;/code&gt;：&lt;/p&gt;</summary>
    
    
    
    
    <category term="Java" scheme="https://www.liuwj.me/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>Ktorm - 让你的数据库操作更具 Kotlin 风味</title>
    <link href="https://www.liuwj.me/posts/ktorm-write-database-operations-in-kotlin-style/"/>
    <id>https://www.liuwj.me/posts/ktorm-write-database-operations-in-kotlin-style/</id>
    <published>2019-06-28T00:00:00.000Z</published>
    <updated>2026-03-01T02:56:55.951Z</updated>
    
    <content type="html"><![CDATA[<p>在上篇文章中，我们介绍了 Ktorm 的基本使用方法。Ktorm 是一个专注于 Kotlin 的 ORM 框架，它提供的 SQL DSL 和序列 API 可以让我们方便地进行数据库操作。在这篇文章中，我们将学习到更多细节，了解 Ktorm 如何让我们的数据库操作更具 Kotlin 风味。</p><blockquote><p>前文地址：<a href="https://www.liuwj.me/posts/ktorm-introduction/">你还在用 MyBatis 吗，Ktorm 了解一下？</a><br />Ktorm 官网：<a href="https://www.ktorm.org/zh-cn/">https://www.ktorm.org</a></p></blockquote><p>在开始之前，我们先回顾一下上篇文章中的员工-部门表的例子，这次我们的示例也是基于这两个表。下面是使用 Ktorm 定义的这两个表的结构：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">object</span> Departments : Table&lt;<span class="built_in">Nothing</span>&gt;(<span class="string">&quot;t_department&quot;</span>) &#123;</span><br><span class="line">    <span class="keyword">val</span> id = int(<span class="string">&quot;id&quot;</span>).primaryKey()    <span class="comment">// Column&lt;Int&gt;</span></span><br><span class="line">    <span class="keyword">val</span> name = varchar(<span class="string">&quot;name&quot;</span>)         <span class="comment">// Column&lt;String&gt;</span></span><br><span class="line">    <span class="keyword">val</span> location = varchar(<span class="string">&quot;location&quot;</span>) <span class="comment">// Column&lt;String&gt;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">object</span> Employees : Table&lt;<span class="built_in">Nothing</span>&gt;(<span class="string">&quot;t_employee&quot;</span>) &#123;</span><br><span class="line">    <span class="keyword">val</span> id = int(<span class="string">&quot;id&quot;</span>).primaryKey()</span><br><span class="line">    <span class="keyword">val</span> name = varchar(<span class="string">&quot;name&quot;</span>)</span><br><span class="line">    <span class="keyword">val</span> job = varchar(<span class="string">&quot;job&quot;</span>)</span><br><span class="line">    <span class="keyword">val</span> managerId = int(<span class="string">&quot;manager_id&quot;</span>)</span><br><span class="line">    <span class="keyword">val</span> hireDate = date(<span class="string">&quot;hire_date&quot;</span>)</span><br><span class="line">    <span class="keyword">val</span> salary = long(<span class="string">&quot;salary&quot;</span>)</span><br><span class="line">    <span class="keyword">val</span> departmentId = int(<span class="string">&quot;department_id&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><span id="more"></span><p>在上面的表定义中，我们可以看到，Ktorm 一般使用 Kotlin 中的 object 关键字定义一个继承 <code>Table</code> 类的对象来描述表结构。这里的 <code>Departments</code> 和 <code>Employees</code> 都继承了 <code>Table</code>，并且在构造函数中指定了表名。表中的列使用 val 关键字定义为表对象中的成员属性，列的类型通过 <code>int</code>、<code>long</code>、<code>varchar</code>、<code>date</code> 等函数定义，它们分别对应了 SQL 中的相应类型。</p><p>在 Ktorm 中，<code>int</code>、<code>long</code>、<code>varchar</code>、<code>date</code> 这类函数称为列定义函数，它们的功能是在当前表中增加一条指定名称和类型的列。Ktorm 内置了许多列定义函数，它们基本涵盖了关系数据库所支持的大部分数据类型。但是，在某些情况下，我们需要在数据库中保存一些原生 JDBC 所不支持的特殊类型的数据（比如 json），这就要求框架能给我们提供扩展数据类型的方式。</p><h2 id="使用扩展函数支持更多数据类型"><a class="markdownIt-Anchor" href="#使用扩展函数支持更多数据类型"></a> 使用扩展函数支持更多数据类型</h2><p><code>SqlType</code> 是 Ktorm 中的一个抽象类，它为 SQL 中的数据类型提供了统一的抽象，要扩展自己的数据类型，我们首先需要提供一个自己的 <code>SqlType</code> 实现类。下面的 <code>JsonSqlType</code> 使用 Jackson 框架进行 json 与对象之间的转换，提供了 json 数据类型的支持：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">JsonSqlType</span>&lt;<span class="type">T : Any</span>&gt;(</span><br><span class="line">    <span class="keyword">val</span> objectMapper: ObjectMapper,</span><br><span class="line">    <span class="keyword">val</span> javaType: JavaType</span><br><span class="line">) : SqlType&lt;T&gt;(Types.VARCHAR, <span class="string">&quot;json&quot;</span>) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">doSetParameter</span><span class="params">(ps: <span class="type">PreparedStatement</span>, index: <span class="type">Int</span>, parameter: <span class="type">T</span>)</span></span> &#123;</span><br><span class="line">        ps.setString(index, objectMapper.writeValueAsString(parameter))</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">doGetResult</span><span class="params">(rs: <span class="type">ResultSet</span>, index: <span class="type">Int</span>)</span></span>: T? &#123;</span><br><span class="line">        <span class="keyword">val</span> json = rs.getString(index)</span><br><span class="line">        <span class="keyword">if</span> (json.isNullOrBlank()) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span></span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> objectMapper.readValue(json, javaType)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>有了 <code>JsonSqlType</code> 之后，接下来的问题就是如何在表对象中添加一条 json 类型的列。我们已经知道，<code>int</code>、<code>varchar</code> 等内置列定义函数的功能正是在<strong>当前表对象</strong>中注册一条相应类型的列，那么我们能不能自己写一个列定义函数呢？</p><p>如果我们用的是 Java，这时恐怕只能遗憾地放弃了，但是 Kotlin 不一样，它支持扩展函数！Kotlin 的扩展函数可以让我们方便地扩展一个已经存在的类，为它添加额外的函数。</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">inline</span> <span class="function"><span class="keyword">fun</span> <span class="type">&lt;<span class="keyword">reified</span> C : Any&gt;</span> BaseTable<span class="type">&lt;*&gt;</span>.<span class="title">json</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">    name: <span class="type">String</span>,</span></span></span><br><span class="line"><span class="params"><span class="function">    mapper: <span class="type">ObjectMapper</span> = sharedObjectMapper</span></span></span><br><span class="line"><span class="params"><span class="function">)</span></span>: Column&lt;C&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> registerColumn(name, JsonSqlType(mapper, mapper.constructType(typeOf&lt;C&gt;())))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用上面这个扩展函数，我们可以很方便地在当前表对象中添加一条 json 类型的列，它的用法和 Ktorm 内置的列定义函数没有任何区别。</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">object</span> Employees : Table&lt;<span class="built_in">Nothing</span>&gt;(<span class="string">&quot;t_employee&quot;</span>) &#123;</span><br><span class="line">    <span class="keyword">val</span> hobbies = json&lt;List&lt;String&gt;&gt;(<span class="string">&quot;hobbies&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>扩展函数是 Kotlin 的一项重要特性，可以让我们在不修改一个类的情况下，为它添加额外的属性和函数，这极大地提高了我们编程的灵活性。Ktorm 对扩展函数有许多的应用，它的绝大部分 API 都是通过扩展函数的方式来提供的。实际上，前面提到的 <code>int</code>、<code>varchar</code> 等内置列定义函数也都是通过扩展函数实现的。</p><h2 id="使用-dsl-编写-sql"><a class="markdownIt-Anchor" href="#使用-dsl-编写-sql"></a> 使用 DSL 编写 SQL</h2><p>DSL（Domain Specific Language，领域特定语言）是专为解决某一特定问题而设计的语言。与通用编程语言相比，DSL 更趋向于声明式，能够更加简洁地表达特定领域的操作。Kotlin 为我们提供了构建内部 DSL 的强大能力，所谓内部 DSL，即<strong>使用 Kotlin 语言开发的，解决特定领域问题，具备独特代码结构的 API</strong>。</p><p>在代码中拼接 SQL 字符串一直是各位程序员心中的痛，Ktorm 提供了强类型的 DSL，让我们可以使用更安全和简便的方式编写 SQL。下面是一个使用 DSL 的例子，它查询每个部门的员工数量，并把部门按人数从高到低排序：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">database</span><br><span class="line">    .from(Departments)</span><br><span class="line">    .innerJoin(Employees, on = Departments.id eq Employees.departmentId)</span><br><span class="line">    .select(Departments.name, count(Employees.id))</span><br><span class="line">    .groupBy(Departments.name)</span><br><span class="line">    .orderBy(count(Employees.id).desc())</span><br><span class="line">    .forEach &#123; row -&gt;</span><br><span class="line">        println(<span class="string">&quot;Dept Name: <span class="subst">$&#123;row.getString(<span class="number">1</span>)&#125;</span>, Emp Count: <span class="subst">$&#123;row.getInt(<span class="number">2</span>)&#125;</span>&quot;</span>)</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>当你运行这段代码，Ktorm 会自动执行一条 SQL，生成的 SQL 如下：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> t_department.name <span class="keyword">as</span> t_department_name, <span class="built_in">count</span>(t_employee.id) </span><br><span class="line"><span class="keyword">from</span> t_department </span><br><span class="line"><span class="keyword">inner</span> <span class="keyword">join</span> t_employee <span class="keyword">on</span> t_department.id <span class="operator">=</span> t_employee.department_id </span><br><span class="line"><span class="keyword">group</span> <span class="keyword">by</span> t_department.name </span><br><span class="line"><span class="keyword">order</span> <span class="keyword">by</span> <span class="built_in">count</span>(t_employee.id) <span class="keyword">desc</span> </span><br></pre></td></tr></table></figure><p>这就是 Kotlin 的魔法，使用 Ktorm 写查询十分地简单和自然，所生成的 SQL 几乎和 Kotlin 代码一一对应。并且，Ktorm 是强类型的，编译器会在你的代码运行之前对它进行检查，IDE 也能对你的代码进行智能提示和自动补全。</p><p>除了查询以外，Ktorm 的 DSL 还支持插入和修改数据，比如向表中插入一名新员工：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">database.insert(Employees) &#123;</span><br><span class="line">    <span class="keyword">set</span>(it.name, <span class="string">&quot;marry&quot;</span>)</span><br><span class="line">    <span class="keyword">set</span>(it.job, <span class="string">&quot;trainee&quot;</span>)</span><br><span class="line">    <span class="keyword">set</span>(it.managerId, <span class="number">1</span>)</span><br><span class="line">    <span class="keyword">set</span>(it.hireDate, LocalDate.now())</span><br><span class="line">    <span class="keyword">set</span>(it.salary, <span class="number">50</span>)</span><br><span class="line">    <span class="keyword">set</span>(it.departmentId, <span class="number">1</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>生成 SQL：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">insert into</span> t_employee (name, job, manager_id, hire_date, salary, department_id) </span><br><span class="line"><span class="keyword">values</span> (?, ?, ?, ?, ?, ?) </span><br></pre></td></tr></table></figure><p>给名为 vince 的员工加一个小目标的薪水<i class="emoji emoji-yum"></i>：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">database.update(Employees) &#123;</span><br><span class="line">    <span class="keyword">set</span>(it.salary, it.salary + <span class="number">100000000</span>)</span><br><span class="line">    <span class="keyword">where</span> &#123;</span><br><span class="line">        it.name eq <span class="string">&quot;vince&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>生成 SQL：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">update</span> t_employee <span class="keyword">set</span> salary <span class="operator">=</span> salary <span class="operator">+</span> ? <span class="keyword">where</span> name <span class="operator">=</span> ? </span><br></pre></td></tr></table></figure><h2 id="运算符重载"><a class="markdownIt-Anchor" href="#运算符重载"></a> 运算符重载</h2><p>在前面给 vince 加薪的过程中，细心的同学可能会发现我们很自然地使用了一个加号：<code>it.salary + 100000000</code>。然而，<code>Employees.salary</code> 的类型是 <code>Column&lt;Long&gt;</code>，我们怎么能把它和一个数字相加呢。这是因为 Kotlin 允许我们对运算符进行重载，使用 operator 关键字修饰的名为 <code>plus</code> 的函数定义了一个加号运算符。当我们对一个 <code>Column</code> 使用加号时，Kotlin 实际上调用了 Ktorm 中的这个 <code>plus</code> 函数：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">operator</span> <span class="function"><span class="keyword">fun</span> <span class="type">&lt;T : Number&gt;</span> Column<span class="type">&lt;T&gt;</span>.<span class="title">plus</span><span class="params">(argument: <span class="type">T</span>)</span></span>: BinaryExpression&lt;T&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> BinaryExpression(</span><br><span class="line">        type = BinaryExpressionType.PLUS, </span><br><span class="line">        left = <span class="keyword">this</span>.asExpression(), </span><br><span class="line">        right = <span class="keyword">this</span>.wrapArgument(argument), </span><br><span class="line">        sqlType = <span class="keyword">this</span>.sqlType</span><br><span class="line">    )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面的函数重载了加号运算符，但它并没有真正执行加法运算，它只是返回了一个 SQL 表达式，这个表达式最终会被 <code>SqlFormatter</code> 翻译为 SQL 中的加号。通过这种方式，Ktorm 得以将 Kotlin 中的四则运算符翻译为 SQL 中的相应符号。</p><p>除了加号以外，Ktorm 还重载了许多常用的运算符，它们包括加号、减号、一元加号、一元减号、乘号、除号、取余、取反等。下面的例子使用取余符号 % 查询数据库中 ID 为奇数的员工：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> query = database.from(Employees).select().<span class="keyword">where</span> &#123; Employees.id % <span class="number">2</span> eq <span class="number">1</span> &#125;</span><br></pre></td></tr></table></figure><p>生成 SQL：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> t_employee <span class="keyword">where</span> (t_employee.id <span class="operator">%</span> ?) <span class="operator">=</span> ? </span><br></pre></td></tr></table></figure><h2 id="通过-infix-定义自己的运算符"><a class="markdownIt-Anchor" href="#通过-infix-定义自己的运算符"></a> 通过 infix 定义自己的运算符</h2><p>通过运算符重载，Ktorm 能够将 Kotlin 中四则运算符翻译为 SQL 中的相应符号。但是 Kotlin 的运算符重载还有许多的限制，比如：</p><ul><li>判等运算符（<code>equals</code> 方法）的返回值类型必须是 <code>Boolean</code>。然而，为了将 Kotlin 中的运算符翻译到 SQL，Ktorm 要求运算符函数必须返回一个 <code>SqlExpression</code>，以记录我们的表达式的语法结构（比如上文中的 <code>plus</code> 函数）。</li><li>支持的运算符有限，无法支持 SQL 中的特殊运算符，比如 <code>like</code>。</li></ul><p>天无绝人之路，Kotlin 提供了 infix 修饰符，使用 infix 修饰的函数，在调用时可以省略点和括号，这为我们开启了另一个思路。比如，使用 infix 关键字修饰 <code>eq</code> 函数，用来支持判等操作，这个 <code>eq</code> 函数我们再前面已经用过许多次：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">infix</span> <span class="function"><span class="keyword">fun</span> <span class="type">&lt;T : Any&gt;</span> Column<span class="type">&lt;T&gt;</span>.<span class="title">eq</span><span class="params">(expr: <span class="type">Column</span>&lt;<span class="type">T</span>&gt;)</span></span>: BinaryExpression&lt;<span class="built_in">Boolean</span>&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> BinaryExpression(</span><br><span class="line">        type = BinaryExpressionType.EQUAL, </span><br><span class="line">        left = <span class="keyword">this</span>.asExpression(), </span><br><span class="line">        right = expr.asExpression(), </span><br><span class="line">        sqlType = BooleanSqlType</span><br><span class="line">    )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>除了 <code>eq</code> 函数外，Ktorm 还提供了许多常用的运算符函数，它们包括 <code>and</code>、<code>or</code>、<code>gt</code>、<code>lt</code>、<code>like</code> 等。不仅如此，我们还能通过 infix 关键字定义自己特殊的运算符，比如 PostgreSQL 中的 <code>ilike</code> 运算符就可以定义为这样的一个 infix 函数：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">infix</span> <span class="function"><span class="keyword">fun</span> Column<span class="type">&lt;*&gt;</span>.<span class="title">ilike</span><span class="params">(argument: <span class="type">String</span>)</span></span>: ILikeExpression &#123;</span><br><span class="line">    <span class="keyword">return</span> ILikeExpression(asExpression(), ArgumentExpression(argument, VarcharSqlType)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>有了这个 <code>ilike</code> 函数，接下来就只需要在 <code>SqlFormatter</code> 中把这个 <code>ILikeExpression</code> 翻译为合适的 SQL 就可以了，Ktorm 给我们提供了足够的灵活性，具体可以参考<a href="https://www.ktorm.org/zh-cn/operators.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E8%BF%90%E7%AE%97%E7%AC%A6">自定义运算符</a>相关的文档。</p><h2 id="sequence-api-像集合一样操作数据库"><a class="markdownIt-Anchor" href="#sequence-api-像集合一样操作数据库"></a> Sequence API 像集合一样操作数据库</h2><p>除了 SQL DSL 以外，Ktorm 还提供了一套名为“实体序列”的 API，用来从数据库中获取实体对象。正如其名字所示，它的风格和使用方式与 Kotlin 标准库中的序列 API 及其类似，它提供了许多同名的扩展函数，比如 <code>filter</code>、<code>map</code>、<code>reduce</code> 等。</p><p>要使用实体序列 API，我们首先要定义实体类，并把表对象与实体类进行绑定：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">Employee</span> : <span class="type">Entity</span>&lt;<span class="type">Employee</span>&gt; &#123;</span><br><span class="line">    <span class="keyword">val</span> id: <span class="built_in">Int</span>?</span><br><span class="line">    <span class="keyword">var</span> name: String</span><br><span class="line">    <span class="keyword">var</span> job: String</span><br><span class="line">    <span class="keyword">var</span> manager: Employee?</span><br><span class="line">    <span class="keyword">var</span> hireDate: LocalDate</span><br><span class="line">    <span class="keyword">var</span> salary: <span class="built_in">Long</span></span><br><span class="line">    <span class="keyword">var</span> department: Department</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">object</span> Employees : Table&lt;Employee&gt;(<span class="string">&quot;t_employee&quot;</span>) &#123;</span><br><span class="line">    <span class="keyword">val</span> id = int(<span class="string">&quot;id&quot;</span>).primaryKey().bindTo &#123; it.id &#125;</span><br><span class="line">    <span class="keyword">val</span> name = varchar(<span class="string">&quot;name&quot;</span>).bindTo &#123; it.name &#125;</span><br><span class="line">    <span class="keyword">val</span> job = varchar(<span class="string">&quot;job&quot;</span>).bindTo &#123; it.job &#125;</span><br><span class="line">    <span class="keyword">val</span> managerId = int(<span class="string">&quot;manager_id&quot;</span>).bindTo &#123; it.manager.id &#125;</span><br><span class="line">    <span class="keyword">val</span> hireDate = date(<span class="string">&quot;hire_date&quot;</span>).bindTo &#123; it.hireDate &#125;</span><br><span class="line">    <span class="keyword">val</span> salary = long(<span class="string">&quot;salary&quot;</span>).bindTo &#123; it.salary &#125;</span><br><span class="line">    <span class="keyword">val</span> departmentId = int(<span class="string">&quot;department_id&quot;</span>).references(Departments) &#123; it.department &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">val</span> Database.employees <span class="keyword">get</span>() = <span class="keyword">this</span>.sequenceOf(Employees)</span><br></pre></td></tr></table></figure><p>完成 ORM 绑定后，我们就可以使用实体序列的各种方便的扩展函数。比如获取部门 1 中工资超过一千的所有员工对象：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> employees = database.employees</span><br><span class="line">    .filter &#123; it.departmentId eq <span class="number">1</span> &#125;</span><br><span class="line">    .filter &#123; it.salary gt <span class="number">1000</span> &#125;</span><br><span class="line">    .toList()</span><br></pre></td></tr></table></figure><p>可以看到，实体序列的用法几乎与 <code>kotlin.sequences.Sequence</code> 完全一样，不同的仅仅是在 lambda 表达式中的等号 <code>==</code> 和大于号 <code>&gt;</code> 被这里的 <code>eq</code> 和 <code>gt</code> 函数代替了而已。</p><p>我们还能使用 <code>mapColumns</code> 函数筛选需要的列，而不必把所有的列都查询出来，以及使用 <code>sortedBy</code> 函数把记录按指定的列进行排序。下面的代码获取部门 1 中工资超过一千的所有员工的名字，并按其工资的高低从大到小排序：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> names = database.employees</span><br><span class="line">    .filter &#123; it.departmentId eq <span class="number">1</span> &#125;</span><br><span class="line">    .filter &#123; it.salary gt <span class="number">1000L</span> &#125;</span><br><span class="line">    .sortedBy &#123; it.salary &#125;</span><br><span class="line">    .mapColumns &#123; it.name &#125;</span><br></pre></td></tr></table></figure><p>生成的 SQL 正如我们所料：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> t_employee.name </span><br><span class="line"><span class="keyword">from</span> t_employee </span><br><span class="line"><span class="keyword">left</span> <span class="keyword">join</span> t_department _ref0 <span class="keyword">on</span> t_employee.department_id <span class="operator">=</span> _ref0.id </span><br><span class="line"><span class="keyword">where</span> (t_employee.department_id <span class="operator">=</span> ?) <span class="keyword">and</span> (t_employee.salary <span class="operator">&gt;</span> ?) </span><br><span class="line"><span class="keyword">order</span> <span class="keyword">by</span> t_employee.salary </span><br></pre></td></tr></table></figure><p>不仅如此，我们还能使用聚合功能，获取每个部门的平均工资：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> averageSalaries = database.employees</span><br><span class="line">    .groupingBy &#123; it.departmentId &#125;</span><br><span class="line">    .eachAverageBy &#123; it.salary &#125;</span><br></pre></td></tr></table></figure><p>生成 SQL：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> t_employee.department_id, <span class="built_in">avg</span>(t_employee.salary) </span><br><span class="line"><span class="keyword">from</span> t_employee </span><br><span class="line"><span class="keyword">group</span> <span class="keyword">by</span> t_employee.department_id </span><br></pre></td></tr></table></figure><p>使用 Ktorm 的实体序列 API，可以让我们的数据库操作看起来就像在使用 Kotlin 中的集合一样。值得注意的是，实体序列 API 并没有真正实现 Kotlin 中的 <code>Sequence</code> 接口，Ktorm 只不过是设计了一套与其命名相似函数，以降低用户学习的成本，同时提供与 Kotlin 集合操作体验一致的编码风格。</p><h2 id="小结"><a class="markdownIt-Anchor" href="#小结"></a> 小结</h2><p>在本文中，我们结合 Kotlin 的一些语法特性，探索了 Ktorm 框架中的许多设计细节。我们学习了如何使用扩展函数为 Ktorm 增加更多数据类型的支持、如何使用强类型的 DSL 编写 SQL、如何使用运算符重载和 infix 关键字为 Ktorm 扩展更多的运算符、以及如何使用实体序列 API 像集合一样操作数据库等。通过对这些细节的探讨，我们看到了 Ktorm 是如何充分利用 Kotlin 的优秀语法特性，帮助我们写出更优雅的、更具 Kotlin 风味的数据库操作代码。</p><p>Enjoy Ktorm, enjoy Kotlin!</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;在上篇文章中，我们介绍了 Ktorm 的基本使用方法。Ktorm 是一个专注于 Kotlin 的 ORM 框架，它提供的 SQL DSL 和序列 API 可以让我们方便地进行数据库操作。在这篇文章中，我们将学习到更多细节，了解 Ktorm 如何让我们的数据库操作更具 Kotlin 风味。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;前文地址：&lt;a href=&quot;https://www.liuwj.me/posts/ktorm-introduction/&quot;&gt;你还在用 MyBatis 吗，Ktorm 了解一下？&lt;/a&gt;&lt;br /&gt;
Ktorm 官网：&lt;a href=&quot;https://www.ktorm.org/zh-cn/&quot;&gt;https://www.ktorm.org&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在开始之前，我们先回顾一下上篇文章中的员工-部门表的例子，这次我们的示例也是基于这两个表。下面是使用 Ktorm 定义的这两个表的结构：&lt;/p&gt;
&lt;figure class=&quot;highlight kotlin&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;object&lt;/span&gt; Departments : Table&amp;lt;&lt;span class=&quot;built_in&quot;&gt;Nothing&lt;/span&gt;&amp;gt;(&lt;span class=&quot;string&quot;&gt;&amp;quot;t_department&amp;quot;&lt;/span&gt;) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;val&lt;/span&gt; id = int(&lt;span class=&quot;string&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;).primaryKey()    &lt;span class=&quot;comment&quot;&gt;// Column&amp;lt;Int&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;val&lt;/span&gt; name = varchar(&lt;span class=&quot;string&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;)         &lt;span class=&quot;comment&quot;&gt;// Column&amp;lt;String&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;val&lt;/span&gt; location = varchar(&lt;span class=&quot;string&quot;&gt;&amp;quot;location&amp;quot;&lt;/span&gt;) &lt;span class=&quot;comment&quot;&gt;// Column&amp;lt;String&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;object&lt;/span&gt; Employees : Table&amp;lt;&lt;span class=&quot;built_in&quot;&gt;Nothing&lt;/span&gt;&amp;gt;(&lt;span class=&quot;string&quot;&gt;&amp;quot;t_employee&amp;quot;&lt;/span&gt;) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;val&lt;/span&gt; id = int(&lt;span class=&quot;string&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;).primaryKey()&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;val&lt;/span&gt; name = varchar(&lt;span class=&quot;string&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;val&lt;/span&gt; job = varchar(&lt;span class=&quot;string&quot;&gt;&amp;quot;job&amp;quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;val&lt;/span&gt; managerId = int(&lt;span class=&quot;string&quot;&gt;&amp;quot;manager_id&amp;quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;val&lt;/span&gt; hireDate = date(&lt;span class=&quot;string&quot;&gt;&amp;quot;hire_date&amp;quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;val&lt;/span&gt; salary = long(&lt;span class=&quot;string&quot;&gt;&amp;quot;salary&amp;quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;val&lt;/span&gt; departmentId = int(&lt;span class=&quot;string&quot;&gt;&amp;quot;department_id&amp;quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    
    <category term="Kotlin" scheme="https://www.liuwj.me/tags/Kotlin/"/>
    
    <category term="Ktorm" scheme="https://www.liuwj.me/tags/Ktorm/"/>
    
  </entry>
  
  <entry>
    <title>你还在用 MyBatis 吗，Ktorm 了解一下？</title>
    <link href="https://www.liuwj.me/posts/ktorm-introduction/"/>
    <id>https://www.liuwj.me/posts/ktorm-introduction/</id>
    <published>2019-05-04T00:00:00.000Z</published>
    <updated>2026-03-01T02:56:55.951Z</updated>
    
    <content type="html"><![CDATA[<p>自从 Google 宣布 Kotlin 成为 Android 的官方语言，Kotlin 可以说是突然火了一波。其实不仅仅是 Android，在服务端开发的领域，Kotlin 也可以说是优势明显。由于其支持空安全、方法扩展、协程等众多的优良特性，以及与 Java 几乎完美的兼容性，选择 Kotlin 可以说是好处多多。</p><p>然而，切换到 Kotlin 之后，你还在用 MyBatis 吗？MyBatis 作为一个 Java 的 SQL 映射框架，虽然在国内使用人数众多，但是也受到了许多吐槽。使用 MyBatis，你必须要忍受在 XML 里写 SQL 这种奇怪的操作，以及在众多 XML 与 Java 接口文件之间跳来跳去的麻烦，以及往 XML 中传递多个参数时的一坨坨 <code>@Param</code> 注解(或者你使用 <code>Map</code>？那就更糟了，连基本的类型校验都没有，参数名也容易写错)。甚至，在与 Kotlin 共存的时候，还会出现一些奇怪的问题，比如： <a href="https://mp.weixin.qq.com/s?__biz=MzIzMTYzOTYzNA==&amp;mid=2247483908&amp;idx=1&amp;sn=0c072a630198d4a23a7d3aec700c138b&amp;chksm=e8a05d39dfd7d42f1494c5f0fcc0562112be6d8912e44fc51f17dee472f9ebdfaad7d930e3b1#rd">Kotlin 遇到 MyBatis：到底是 Int 的错，还是 data class 的错？</a>。</p><p>这时，你可能想要一款专属于 Kotlin 的 ORM 框架。它可以充分利用 Kotlin 的各种优良特性，让我们写出更加 Kotlin 的代码。它应该是轻量级的，只需要添加依赖即可直接使用，不需要各种麻烦的配置文件。它的 SQL 最好可以自动生成，不需要像 MyBatis 那样每条 SQL 都自己写，但是也给我们保留精确控制 SQL 的能力，不至于像 Hibernate 那样难以进行 SQL 调优。</p><p>如果你真的这么想的话，Ktorm 可能会适合你。Ktorm 是直接基于纯 JDBC 编写的高效简洁的 Kotlin ORM 框架，它提供了强类型而且灵活的 SQL DSL 和方便的序列 API，以减少我们操作数据库的重复劳动。当然，所有的 SQL 都是自动生成的。本文的目的就是对 Ktorm 进行介绍，帮助我们快速上手使用。</p><span id="more"></span><blockquote><p>你可以在 Ktorm 的官网上获取更详细的使用文档，如果使用遇到问题，还可以在 GitHub 提出 issue。如果 Ktorm 对你有帮助的话，请在 GitHub 留下你的 star，也欢迎加入我们，共同打造 Kotlin 优雅的 ORM 解决方案。</p><p>Ktorm 官网：<a href="https://www.ktorm.org/zh-cn/">https://www.ktorm.org</a><br />GitHub 地址：<a href="https://github.com/kotlin-orm/ktorm">https://github.com/kotlin-orm/ktorm</a></p></blockquote><h2 id="hello-ktorm"><a class="markdownIt-Anchor" href="#hello-ktorm"></a> Hello, Ktorm!</h2><p>还记得我们刚开始学编程的时候写的第一个程序吗，现在我们先从 Ktorm 的 “Hello, World” 开始，了解如何快速地搭建一个使用 Ktorm 的项目。</p><p>Ktorm 已经发布到 maven 中央仓库和 jcenter，因此，如果你使用 maven 的话，首先需要在 <code>pom.xml</code> 文件里面添加一个依赖：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.ktorm<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>ktorm-core<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>$&#123;ktorm.version&#125;<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>或者 gradle：</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">compile <span class="string">&quot;org.ktorm:ktorm-core:$&#123;ktorm.version&#125;&quot;</span></span><br></pre></td></tr></table></figure><p>在使用 Ktorm 之前，我们需要让它能够了解我们的表结构。假设我们有两个表，他们分别是部门表 <code>t_department</code> 和员工表 <code>t_employee</code>， 它们的建表 SQL 如下，我们要如何描述这两个表呢？</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">create table</span> t_department(</span><br><span class="line">  id <span class="type">int</span> <span class="keyword">not null</span> <span class="keyword">primary key</span> auto_increment,</span><br><span class="line">  name <span class="type">varchar</span>(<span class="number">128</span>) <span class="keyword">not null</span>,</span><br><span class="line">  location <span class="type">varchar</span>(<span class="number">128</span>) <span class="keyword">not null</span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">create table</span> t_employee(</span><br><span class="line">  id <span class="type">int</span> <span class="keyword">not null</span> <span class="keyword">primary key</span> auto_increment,</span><br><span class="line">  name <span class="type">varchar</span>(<span class="number">128</span>) <span class="keyword">not null</span>,</span><br><span class="line">  job <span class="type">varchar</span>(<span class="number">128</span>) <span class="keyword">not null</span>,</span><br><span class="line">  manager_id <span class="type">int</span> <span class="keyword">null</span>,</span><br><span class="line">  hire_date <span class="type">date</span> <span class="keyword">not null</span>,</span><br><span class="line">  salary <span class="type">bigint</span> <span class="keyword">not null</span>,</span><br><span class="line">  department_id <span class="type">int</span> <span class="keyword">not null</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>一般来说，Ktorm 使用 Kotlin 中的 object 关键字定义一个继承 <code>Table</code> 类的对象来描述表结构，上面例子中的两个表可以像这样在 Ktorm 中定义：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">object</span> Departments : Table&lt;<span class="built_in">Nothing</span>&gt;(<span class="string">&quot;t_department&quot;</span>) &#123;</span><br><span class="line">    <span class="keyword">val</span> id = int(<span class="string">&quot;id&quot;</span>).primaryKey()    <span class="comment">// Column&lt;Int&gt;</span></span><br><span class="line">    <span class="keyword">val</span> name = varchar(<span class="string">&quot;name&quot;</span>)         <span class="comment">// Column&lt;String&gt;</span></span><br><span class="line">    <span class="keyword">val</span> location = varchar(<span class="string">&quot;location&quot;</span>) <span class="comment">// Column&lt;String&gt;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">object</span> Employees : Table&lt;<span class="built_in">Nothing</span>&gt;(<span class="string">&quot;t_employee&quot;</span>) &#123;</span><br><span class="line">    <span class="keyword">val</span> id = int(<span class="string">&quot;id&quot;</span>).primaryKey()</span><br><span class="line">    <span class="keyword">val</span> name = varchar(<span class="string">&quot;name&quot;</span>)</span><br><span class="line">    <span class="keyword">val</span> job = varchar(<span class="string">&quot;job&quot;</span>)</span><br><span class="line">    <span class="keyword">val</span> managerId = int(<span class="string">&quot;manager_id&quot;</span>)</span><br><span class="line">    <span class="keyword">val</span> hireDate = date(<span class="string">&quot;hire_date&quot;</span>)</span><br><span class="line">    <span class="keyword">val</span> salary = long(<span class="string">&quot;salary&quot;</span>)</span><br><span class="line">    <span class="keyword">val</span> departmentId = int(<span class="string">&quot;department_id&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到，<code>Departments</code> 和 <code>Employees</code> 都继承了 <code>Table</code>，并且在构造函数中指定了表名，<code>Table</code> 类还有一个泛型参数，它是此表绑定到的实体类的类型，在这里我们不需要绑定到任何实体类，因此指定为 <code>Nothing</code> 即可。表中的列则使用 val 关键字定义为表对象中的成员属性，列的类型使用 int、long、varchar、date 等函数定义，它们分别对应了 SQL 中的相应类型。</p><p>定义好表结构后，我们就可以使用 <code>Database.connect</code> 函数连接到数据库，然后执行一个简单的查询：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">val</span> database = Database.connect(<span class="string">&quot;jdbc:mysql://localhost:3306/ktorm&quot;</span>, user = <span class="string">&quot;root&quot;</span>, password = <span class="string">&quot;***&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (row <span class="keyword">in</span> database.from(Employees).select()) &#123;</span><br><span class="line">        println(row[Employees.name])</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这就是一个最简单的 Ktorm 项目，这个 <code>main</code> 函数中只有短短三四行代码，但是你运行它时，它却可以连接到数据库，自动生成一条 SQL <code>select * from t_employee</code>，查询表中所有的员工记录，然后打印出他们的名字。因为 <code>select</code> 函数返回的查询对象重载了迭代运算符，所以你可以在这里使用 for-each 循环的语法。</p><h2 id="sql-dsl"><a class="markdownIt-Anchor" href="#sql-dsl"></a> SQL DSL</h2><p>让我们在上面的查询里再增加一点筛选条件：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">database</span><br><span class="line">    .from(Employees)</span><br><span class="line">    .select(Employees.name)</span><br><span class="line">    .<span class="keyword">where</span> &#123; (Employees.departmentId eq <span class="number">1</span>) and (Employees.name like <span class="string">&quot;%vince%&quot;</span>) &#125;</span><br><span class="line">    .forEach &#123; row -&gt; </span><br><span class="line">        println(row[Employees.name])</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>生成的 SQL 如下:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> t_employee.name <span class="keyword">as</span> t_employee_name </span><br><span class="line"><span class="keyword">from</span> t_employee </span><br><span class="line"><span class="keyword">where</span> (t_employee.department_id <span class="operator">=</span> ?) <span class="keyword">and</span> (t_employee.name <span class="keyword">like</span> ?)</span><br></pre></td></tr></table></figure><p>这就是 Kotlin 的魔法，使用 Ktorm 写查询十分地简单和自然，所生成的 SQL 几乎和 Kotlin 代码一一对应。并且，Ktorm 是强类型的，编译器会在你的代码运行之前对它进行检查，IDE 也能对你的代码进行智能提示和自动补全。</p><p>实现基于条件的动态查询也十分简单，因为都是纯 Kotlin 代码，直接使用 if 语句就好，比 MyBatis 在 XML 里面写 <code>&lt;if&gt;</code> 标签好太多。</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> query = database</span><br><span class="line">    .from(Employees)</span><br><span class="line">    .select(Employees.name)</span><br><span class="line">    .whereWithConditions &#123;</span><br><span class="line">        <span class="keyword">if</span> (someCondition) &#123;</span><br><span class="line">            it += Employees.managerId.isNull()</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (otherCondition) &#123;</span><br><span class="line">            it += Employees.departmentId eq <span class="number">1</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>聚合查询：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> t = Employees.aliased(<span class="string">&quot;t&quot;</span>)</span><br><span class="line">database</span><br><span class="line">    .from(t)</span><br><span class="line">    .select(t.departmentId, avg(t.salary))</span><br><span class="line">    .groupBy(t.departmentId)</span><br><span class="line">    .having &#123; avg(t.salary) gt <span class="number">100.0</span> &#125;</span><br><span class="line">    .forEach &#123; row -&gt; </span><br><span class="line">        println(<span class="string">&quot;<span class="subst">$&#123;row.getInt(<span class="number">1</span>)&#125;</span>:<span class="subst">$&#123;row.getDouble(<span class="number">2</span>)&#125;</span>&quot;</span>)</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>Union：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> query = database</span><br><span class="line">    .from(Employees)</span><br><span class="line">    .select(Employees.id)</span><br><span class="line">    .unionAll(</span><br><span class="line">        database.from(Departments).select(Departments.id)</span><br><span class="line">    )</span><br><span class="line">    .unionAll(</span><br><span class="line">        database.from(Departments).select(Departments.id)</span><br><span class="line">    )</span><br><span class="line">    .orderBy(Employees.id.desc())</span><br></pre></td></tr></table></figure><p>多表连接查询：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">data</span> <span class="keyword">class</span> <span class="title class_">Names</span>(<span class="keyword">val</span> name: String?, <span class="keyword">val</span> managerName: String?, <span class="keyword">val</span> departmentName: String?)</span><br><span class="line"></span><br><span class="line"><span class="keyword">val</span> emp = Employees.aliased(<span class="string">&quot;emp&quot;</span>)</span><br><span class="line"><span class="keyword">val</span> mgr = Employees.aliased(<span class="string">&quot;mgr&quot;</span>)</span><br><span class="line"><span class="keyword">val</span> dept = Departments.aliased(<span class="string">&quot;dept&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">val</span> results = database</span><br><span class="line">    .from(emp)</span><br><span class="line">    .leftJoin(dept, on = emp.departmentId eq dept.id)</span><br><span class="line">    .leftJoin(mgr, on = emp.managerId eq mgr.id)</span><br><span class="line">    .select(emp.name, mgr.name, dept.name)</span><br><span class="line">    .orderBy(emp.id.asc())</span><br><span class="line">    .map &#123; row -&gt; </span><br><span class="line">        Names(</span><br><span class="line">            name = row[emp.name],</span><br><span class="line">            managerName = row[mgr.name],</span><br><span class="line">            departmentName = row[dept.name]</span><br><span class="line">        )</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>插入：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">database.insert(Employees) &#123;</span><br><span class="line">    <span class="keyword">set</span>(it.name, <span class="string">&quot;jerry&quot;</span>)</span><br><span class="line">    <span class="keyword">set</span>(it.job, <span class="string">&quot;trainee&quot;</span>)</span><br><span class="line">    <span class="keyword">set</span>(it.managerId, <span class="number">1</span>)</span><br><span class="line">    <span class="keyword">set</span>(it.hireDate, LocalDate.now())</span><br><span class="line">    <span class="keyword">set</span>(it.salary, <span class="number">50</span>)</span><br><span class="line">    <span class="keyword">set</span>(it.departmentId, <span class="number">1</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>更新：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">database.update(Employees) &#123;</span><br><span class="line">    <span class="keyword">set</span>(it.job, <span class="string">&quot;engineer&quot;</span>)</span><br><span class="line">    <span class="keyword">set</span>(it.managerId, <span class="literal">null</span>)</span><br><span class="line">    <span class="keyword">set</span>(it.salary, <span class="number">100</span>)</span><br><span class="line">    <span class="keyword">where</span> &#123;</span><br><span class="line">        it.id eq <span class="number">2</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>删除：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">database.delete(Employees) &#123; it.id eq <span class="number">4</span> &#125;</span><br></pre></td></tr></table></figure><p>这就是 Ktorm 提供的 SQL DSL，使用这套 DSL，我们可以使用纯 Kotlin 代码来编写查询，不再需要在 XML 中写 SQL，也不需要在代码中拼接 SQL 字符串。而且，强类型的 DSL 还能让我们获得一些额外的好处，比如将一些低级的错误暴露在编译期，以及 IDE 的智能提示和自动补全。最重要的是，它生成的 SQL 几乎与我们的 Kotlin 代码一一对应，因此虽然我们的 SQL 是自动生成的，我们仍然对它拥有绝对的控制。</p><p>这套 DSL 几乎可以覆盖我们工作中常见的所有 SQL 的用法，比如 union、联表、聚合等，甚至对嵌套查询也有一定的支持。当然，肯定也有一些暂时不支持的用法，比如某些数据库中的特殊语法，或者十分复杂的查询(如相关子查询)。这其实十分罕见，但如果真的发生，Ktorm 也提供了一些解决方案：</p><ul><li>Ktorm 可以方便的对 SQL DSL 进行扩展，以支持某些数据库中的特殊语法，这些扩展主要以独立的 jar 包提供，比如 <code>ktorm-support-mysql</code>。当然，我们也能自己编写扩展。</li><li>对于确实无法支持的情况，Ktorm 也可以直接使用原生 SQL 进行查询，并额外提供了一些方便的扩展函数支持。</li></ul><p>更多 SQL DSL 的用法，请参考 Ktorm 的<a href="https://www.ktorm.org/zh-cn/query.html">具体文档</a>。</p><h2 id="实体类与列绑定"><a class="markdownIt-Anchor" href="#实体类与列绑定"></a> 实体类与列绑定</h2><p>前面我们已经介绍了 SQL DSL，但是如果只有 DSL，Ktorm 还远不能称为一个 ORM 框架。接下来我们将介绍实体类的概念，了解如何将数据库中的表与实体类进行绑定，这正是 ORM 框架的核心：对象 - 关系映射。</p><p>我们仍然以前面的部门表 <code>t_department</code> 和员工表 <code>t_employee</code> 为例，创建两个 Ktorm 的实体类，分别用来表示部门和员工这两个业务概念：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">Department</span> : <span class="type">Entity</span>&lt;<span class="type">Department</span>&gt; &#123;</span><br><span class="line">    <span class="keyword">companion</span> <span class="keyword">object</span> : Entity.Factory&lt;Department&gt;()</span><br><span class="line">    <span class="keyword">val</span> id: <span class="built_in">Int</span></span><br><span class="line">    <span class="keyword">var</span> name: String</span><br><span class="line">    <span class="keyword">var</span> location: String</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">Employee</span> : <span class="type">Entity</span>&lt;<span class="type">Employee</span>&gt; &#123;</span><br><span class="line">    <span class="keyword">companion</span> <span class="keyword">object</span> : Entity.Factory&lt;Employee&gt;()</span><br><span class="line">    <span class="keyword">val</span> id: <span class="built_in">Int</span>?</span><br><span class="line">    <span class="keyword">var</span> name: String</span><br><span class="line">    <span class="keyword">var</span> job: String</span><br><span class="line">    <span class="keyword">var</span> manager: Employee?</span><br><span class="line">    <span class="keyword">var</span> hireDate: LocalDate</span><br><span class="line">    <span class="keyword">var</span> salary: <span class="built_in">Long</span></span><br><span class="line">    <span class="keyword">var</span> department: Department</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到，Ktorm 中的实体类都继承了 <code>Entity&lt;E&gt;</code> 接口，这个接口为实体类注入了一些通用的方法。实体类的属性则使用 var 或 val 关键字直接定义即可，根据需要确定属性的类型及是否为空。</p><p>有一点可能会违背你的直觉，Ktorm 中的实体类并不是 data class，甚至也不是一个普通的 class，而是 interface。这是 Ktorm 的设计要求，通过将实体类定义为 interface，Ktorm 才能够实现一些特别的功能，以后你会了解到它的意义。</p><p>众所周知，接口并不能实例化，既然实体类被定义为接口，我们要如何才能创建一个实体对象呢？其实很简单，只需要像下面这样，假装它有一个构造函数：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> department = Department()</span><br></pre></td></tr></table></figure><p>有心的同学应该已经发现，上面定义实体类接口的时候，还为这两个接口都增加了一个伴随对象。这个伴随对象重载了 Kotlin 中的 <code>invoke</code> 操作符，因此可以使用括号像函数一样直接调用。在 Ktorm 的内部，我们使用了 JDK 的动态代理创建了实体对象。</p><p>还记得在上一节中我们定义的两个表对象吗？现在我们已经有了实体类，下一步就是把实体类和前面的表对象进行绑定。这个绑定其实十分简单，只需要在声明列之后继续链式调用 <code>bindTo</code> 函数或 <code>references</code> 函数即可，下面的代码修改了前面的两个表对象，完成了 ORM 绑定：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">object</span> Departments : Table&lt;Department&gt;(<span class="string">&quot;t_department&quot;</span>) &#123;</span><br><span class="line">    <span class="keyword">val</span> id = int(<span class="string">&quot;id&quot;</span>).primaryKey().bindTo &#123; it.id &#125;</span><br><span class="line">    <span class="keyword">val</span> name = varchar(<span class="string">&quot;name&quot;</span>).bindTo &#123; it.name &#125;</span><br><span class="line">    <span class="keyword">val</span> location = varchar(<span class="string">&quot;location&quot;</span>).bindTo &#123; it.location &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">object</span> Employees : Table&lt;Employee&gt;(<span class="string">&quot;t_employee&quot;</span>) &#123;</span><br><span class="line">    <span class="keyword">val</span> id = int(<span class="string">&quot;id&quot;</span>).primaryKey().bindTo &#123; it.id &#125;</span><br><span class="line">    <span class="keyword">val</span> name = varchar(<span class="string">&quot;name&quot;</span>).bindTo &#123; it.name &#125;</span><br><span class="line">    <span class="keyword">val</span> job = varchar(<span class="string">&quot;job&quot;</span>).bindTo &#123; it.job &#125;</span><br><span class="line">    <span class="keyword">val</span> managerId = int(<span class="string">&quot;manager_id&quot;</span>).bindTo &#123; it.manager.id &#125;</span><br><span class="line">    <span class="keyword">val</span> hireDate = date(<span class="string">&quot;hire_date&quot;</span>).bindTo &#123; it.hireDate &#125;</span><br><span class="line">    <span class="keyword">val</span> salary = long(<span class="string">&quot;salary&quot;</span>).bindTo &#123; it.salary &#125;</span><br><span class="line">    <span class="keyword">val</span> departmentId = int(<span class="string">&quot;department_id&quot;</span>).references(Departments) &#123; it.department &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>命名规约：强烈建议使用单数名词命名实体类，使用名词的复数形式命名表对象，如：Employee/Employees、Department/Departments。</p></blockquote><p>把两个表对象与修改前进行对比，我们可以发现两处不同：</p><ol><li><code>Table</code> 类的泛型参数，我们需要指定为实体类的类型，以便 Ktorm 将表对象与实体类进行绑定；在之前，我们设置为 <code>Nothing</code> 表示不绑定到任何实体类。</li><li>在每个列声明函数的调用后，都链式调用了 <code>bindTo</code> 或 <code>references</code> 函数将该列与实体类的某个属性进行绑定；如果没有这个调用，则不会绑定到任何属性。</li></ol><p>列绑定的意义在于，通过查询从数据库中获取实体对象的时候，Ktorm 会根据我们的绑定配置，将某个列的数据填充到它所绑定的属性中去；在将实体对象中的修改更新到数据库中的时候（使用 <code>flushChanges</code> 函数），Ktorm 也会根据我们的绑定配置，将某个属性的变更，同步更新到绑定它的那个列。</p><p>完成列绑定后，我们就可以使用<a href="#%E5%AE%9E%E4%BD%93%E5%BA%8F%E5%88%97-API">序列 API</a> 对实体进行各种灵活的操作。我们先给 <code>Database</code> 定义两个扩展属性，它们使用 <code>sequenceOf</code> 函数创建序列对象并返回。这两个属性可以帮助我们提高代码的可读性：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> Database.departments <span class="keyword">get</span>() = <span class="keyword">this</span>.sequenceOf(Departments)</span><br><span class="line"><span class="keyword">val</span> Database.employees <span class="keyword">get</span>() = <span class="keyword">this</span>.sequenceOf(Employees)</span><br></pre></td></tr></table></figure><p>下面的代码使用 <code>find</code> 函数从序列中根据名字获取一个 Employee 对象：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> employee = database.employees.find &#123; it.name eq <span class="string">&quot;vince&quot;</span> &#125;</span><br></pre></td></tr></table></figure><p>我们还能使用 <code>filter</code> 函数对序列进行筛选，比如获取所有名字为 vince 的员工：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> employees = database.employees.filter &#123; it.name eq <span class="string">&quot;vince&quot;</span> &#125;.toList()</span><br></pre></td></tr></table></figure><p><code>find</code> 和 <code>filter</code> 函数都接受一个 lambda 表达式作为参数，使用该 lambda 的返回值作为条件，生成一条查询 SQL。可以看到，生成的 SQL 自动 left jion 了关联表 <code>t_department</code>：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> </span><br><span class="line"><span class="keyword">from</span> t_employee </span><br><span class="line"><span class="keyword">left</span> <span class="keyword">join</span> t_department _ref0 <span class="keyword">on</span> t_employee.department_id <span class="operator">=</span> _ref0.id </span><br><span class="line"><span class="keyword">where</span> t_employee.name <span class="operator">=</span> ?</span><br></pre></td></tr></table></figure><p>将实体对象保存到数据库：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> employee = Employee &#123;</span><br><span class="line">    name = <span class="string">&quot;jerry&quot;</span></span><br><span class="line">    job = <span class="string">&quot;trainee&quot;</span></span><br><span class="line">    hireDate = LocalDate.now()</span><br><span class="line">    salary = <span class="number">50</span></span><br><span class="line">    department = database.departments.find &#123; it.name eq <span class="string">&quot;tech&quot;</span> &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">database.employees.add(employee)</span><br></pre></td></tr></table></figure><p>将内存中实体对象的变化更新到数据库：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> employee = database.employees.find &#123; it.id eq <span class="number">2</span> &#125; ?: <span class="keyword">return</span></span><br><span class="line">employee.job = <span class="string">&quot;engineer&quot;</span></span><br><span class="line">employee.salary = <span class="number">100</span></span><br><span class="line">employee.flushChanges()</span><br></pre></td></tr></table></figure><p>从数据库中删除实体对象：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> employee = database.employees.find &#123; it.id eq <span class="number">2</span> &#125; ?: <span class="keyword">return</span></span><br><span class="line">employee.delete()</span><br></pre></td></tr></table></figure><p>更多实体 API 的用法，可参考<a href="https://www.ktorm.org/zh-cn/entities-and-column-binding.html">列绑定</a>和<a href="https://www.ktorm.org/zh-cn/entity-finding.html">实体查询</a>相关的文档。</p><p>可以看到，只需要将表对象与实体类进行绑定，我们就可以使用这些方便的函数，大部分对实体对象的增删改查操作，都只需要一个函数调用即可完成，但 Ktorm 能做到的，还远不止于此。</p><h2 id="实体序列-api"><a class="markdownIt-Anchor" href="#实体序列-api"></a> 实体序列 API</h2><p>Ktorm 提供了一套名为”实体序列”的 API，用来从数据库中获取实体对象。正如其名字所示，它的风格和使用方式与 Kotlin 标准库中的序列 API 极其类似，它提供了许多同名的扩展函数，比如 <code>filter</code>、<code>map</code>、<code>reduce</code> 等。</p><p>Ktorm 的实体序列 API，大部分都是以扩展函数的方式提供的，这些扩展函数大致可以分为两类，它们分别是中间操作和终止操作。</p><h3 id="中间操作"><a class="markdownIt-Anchor" href="#中间操作"></a> 中间操作</h3><p>这类操作并不会执行序列中的查询，而是修改并创建一个新的序列对象，比如 <code>filter</code> 函数会使用指定的筛选条件创建一个新的序列对象。下面使用 <code>filter</code> 获取部门 1 中的所有员工：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> employees = database.employees.filter &#123; it.departmentId eq <span class="number">1</span> &#125;.toList()</span><br></pre></td></tr></table></figure><p>可以看到，用法几乎与 <code>kotlin.sequences</code> 完全一样，不同的仅仅是在 lambda 表达式中的等号 <code>==</code> 被这里的 <code>eq</code> 函数代替了而已。<code>filter</code> 函数还可以连续使用，此时所有的筛选条件将使用 <code>and</code> 运算符进行连接，比如：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> employees = database.employees</span><br><span class="line">    .filter &#123; it.departmentId eq <span class="number">1</span> &#125;</span><br><span class="line">    .filter &#123; it.managerId.isNotNull() &#125;</span><br><span class="line">    .toList()</span><br></pre></td></tr></table></figure><p>生成 SQL：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> </span><br><span class="line"><span class="keyword">from</span> t_employee </span><br><span class="line"><span class="keyword">left</span> <span class="keyword">join</span> t_department _ref0 <span class="keyword">on</span> t_employee.department_id <span class="operator">=</span> _ref0.id </span><br><span class="line"><span class="keyword">where</span> (t_employee.department_id <span class="operator">=</span> ?) <span class="keyword">and</span> (t_employee.manager_id <span class="keyword">is</span> <span class="keyword">not null</span>)</span><br></pre></td></tr></table></figure><p>使用 <code>sortedBy</code> 或 <code>sortedByDescending</code> 对序列中的元素进行排序：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> employees = database.employees.sortedBy &#123; it.salary &#125;.toList()</span><br></pre></td></tr></table></figure><p>使用 <code>drop</code> 和 <code>take</code> 函数进行分页：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> employees = database.employees.drop(<span class="number">1</span>).take(<span class="number">1</span>).toList()</span><br></pre></td></tr></table></figure><h3 id="终止操作"><a class="markdownIt-Anchor" href="#终止操作"></a> 终止操作</h3><p>实体序列的终止操作会马上执行一个查询，获取查询的执行结果，然后执行一定的计算。for-each 循环就是一个典型的终止操作，下面我们使用 for-each 循环打印出序列中所有的员工：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (employee <span class="keyword">in</span> database.employees) &#123;</span><br><span class="line">    println(employee)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>生成的 SQL 如下：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> </span><br><span class="line"><span class="keyword">from</span> t_employee </span><br><span class="line"><span class="keyword">left</span> <span class="keyword">join</span> t_department _ref0 <span class="keyword">on</span> t_employee.department_id <span class="operator">=</span> _ref0.id</span><br></pre></td></tr></table></figure><p><code>toCollection</code>、<code>toList</code> 等方法用于将序列中的元素保存为一个集合：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> employees = database.employees.toCollection(ArrayList())</span><br></pre></td></tr></table></figure><p><code>mapColumns</code> 函数用于获取指定列的结果：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> names = database.employees.mapColumns &#123; it.name &#125;</span><br></pre></td></tr></table></figure><p>除此之外，<code>mapColumns</code> 还可以同时获取多个列的结果，这时我们只需要在闭包中使用 <code>tupleOf</code> 包装我们的这些字段，函数的返回值也相应变成了 <code>List&lt;TupleN&lt;C1?, C2?, .. Cn?&gt;&gt;</code>：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">database.employees</span><br><span class="line">    .filter &#123; it.departmentId eq <span class="number">1</span> &#125;</span><br><span class="line">    .mapColumns &#123; tupleOf(it.id, it.name) &#125;</span><br><span class="line">    .forEach &#123; (id, name) -&gt;</span><br><span class="line">        println(<span class="string">&quot;<span class="variable">$id</span>:<span class="variable">$name</span>&quot;</span>)</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>生成 SQL：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> t_employee.id, t_employee.name</span><br><span class="line"><span class="keyword">from</span> t_employee </span><br><span class="line"><span class="keyword">where</span> t_employee.department_id <span class="operator">=</span> ?</span><br></pre></td></tr></table></figure><p>其他我们熟悉的序列函数也都支持，比如 <code>fold</code>、<code>reduce</code>、<code>forEach</code> 等，下面使用 <code>fold</code> 计算所有员工的工资总和：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> totalSalary = database.employees</span><br><span class="line">    .fold(<span class="number">0L</span>) &#123; acc, employee -&gt; </span><br><span class="line">        acc + employee.salary </span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><h3 id="序列聚合"><a class="markdownIt-Anchor" href="#序列聚合"></a> 序列聚合</h3><p>实体序列 API 不仅可以让我们使用类似 <code>kotlin.sequences</code> 的方式获取数据库中的实体对象，它还支持丰富的聚合功能，让我们可以方便地对指定字段进行计数、求和、求平均值等操作。</p><p>下面使用 <code>aggregateColumns</code> 函数获取部门 1 中工资的最大值：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> max = database.employees</span><br><span class="line">    .filter &#123; it.departmentId eq <span class="number">1</span> &#125;</span><br><span class="line">    .aggregateColumns &#123; max(it.salary) &#125;</span><br></pre></td></tr></table></figure><p>如果你希望同时获取多个聚合结果，只需要在闭包中使用 <code>tupleOf</code> 包装我们的这些聚合表达式即可，此时函数的返回值就相应变成了 <code>TupleN&lt;C1?, C2?, .. Cn?&gt;</code>。下面的例子获取部门 1 中工资的平均值和极差：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> (avg, diff) = database.employees</span><br><span class="line">    .filter &#123; it.departmentId eq <span class="number">1</span> &#125;</span><br><span class="line">    .aggregateColumns &#123; tupleOf(avg(it.salary), max(it.salary) - min(it.salary)) &#125;</span><br></pre></td></tr></table></figure><p>生成 SQL：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="built_in">avg</span>(t_employee.salary), <span class="built_in">max</span>(t_employee.salary) <span class="operator">-</span> <span class="built_in">min</span>(t_employee.salary) </span><br><span class="line"><span class="keyword">from</span> t_employee </span><br><span class="line"><span class="keyword">where</span> t_employee.department_id <span class="operator">=</span> ?</span><br></pre></td></tr></table></figure><p>除了直接使用 <code>aggregateColumns</code> 函数以外，Ktorm 还为序列提供了许多方便的辅助函数，他们都是基于 <code>aggregateColumns</code> 函数实现的，分别是 <code>count</code>、<code>any</code>、<code>none</code>、<code>all</code>、<code>sumBy</code>、<code>maxBy</code>、<code>minBy</code>、<code>averageBy</code>。</p><p>下面改用 <code>maxBy</code> 函数获取部门 1 中工资的最大值：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> max = database.employees</span><br><span class="line">    .filter &#123; it.departmentId eq <span class="number">1</span> &#125;</span><br><span class="line">    .maxBy &#123; it.salary &#125;</span><br></pre></td></tr></table></figure><p>除此之外，Ktorm 还支持分组聚合，只需要先调用 <code>groupingBy</code>，再调用 <code>aggregateColumns</code>。下面的代码可以获取所有部门的平均工资，它的返回值类型是 <code>Map&lt;Int?, Double?&gt;</code>，其中键为部门 ID，值是各个部门工资的平均值：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> averageSalaries = database.employees</span><br><span class="line">    .groupingBy &#123; it.departmentId &#125;</span><br><span class="line">    .aggregateColumns &#123; avg(it.salary) &#125;</span><br></pre></td></tr></table></figure><p>生成 SQL：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> t_employee.department_id, <span class="built_in">avg</span>(t_employee.salary) </span><br><span class="line"><span class="keyword">from</span> t_employee </span><br><span class="line"><span class="keyword">group</span> <span class="keyword">by</span> t_employee.department_id</span><br></pre></td></tr></table></figure><p>在分组聚合时，Ktorm 也提供了许多方便的辅助函数，它们是 <code>eachCount(To)</code>、<code>eachSumBy(To)</code>、<code>eachMaxBy(To)</code>、<code>eachMinBy(To)</code>、<code>eachAverageBy(To)</code>。有了这些辅助函数，上面获取所有部门平均工资的代码就可以改写成：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> averageSalaries = database.employees</span><br><span class="line">    .groupingBy &#123; it.departmentId &#125;</span><br><span class="line">    .eachAverageBy &#123; it.salary &#125;</span><br></pre></td></tr></table></figure><p>除此之外，Ktorm 还提供了 <code>aggregate</code>、<code>fold</code>、<code>reduce</code> 等函数，它们与 <code>kotlin.collections.Grouping</code> 的相应函数同名，功能也完全一样。下面的代码使用 <code>fold</code> 函数计算每个部门工资的总和：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> totalSalaries = database.employees</span><br><span class="line">    .groupingBy &#123; it.departmentId &#125;</span><br><span class="line">    .fold(<span class="number">0L</span>) &#123; acc, employee -&gt; </span><br><span class="line">        acc + employee.salary </span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>更多实体序列 API 的用法，可参考<a href="https://www.ktorm.org/zh-cn/entity-sequence.html">实体序列</a>和<a href="https://www.ktorm.org/zh-cn/sequence-aggregation.html">序列聚合</a>相关的文档。</p><h2 id="小结"><a class="markdownIt-Anchor" href="#小结"></a> 小结</h2><p>本文从一个 “Hello, World” 程序开始，对 Ktorm 的几大特性进行了介绍，它们分别是 SQL DSL、实体类与列绑定、实体序列 API 等。有了 Ktorm，我们就可以使用纯 Kotlin 代码方便地完成数据持久层的操作，不需要再使用 MyBatis 烦人的 XML。同时，由于 Ktorm 是专注于 Kotlin 语言的框架，因此没有兼容 Java 的包袱，能够让我们更加充分地使用 Kotlin 各种优越的语法特性，写出更加优雅的代码。既然语言都已经切换到 Kotlin，为何不尝试一下纯 Kotlin 的框架呢？</p><p>Enjoy Ktorm, enjoy Kotlin!</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;自从 Google 宣布 Kotlin 成为 Android 的官方语言，Kotlin 可以说是突然火了一波。其实不仅仅是 Android，在服务端开发的领域，Kotlin 也可以说是优势明显。由于其支持空安全、方法扩展、协程等众多的优良特性，以及与 Java 几乎完美的兼容性，选择 Kotlin 可以说是好处多多。&lt;/p&gt;
&lt;p&gt;然而，切换到 Kotlin 之后，你还在用 MyBatis 吗？MyBatis 作为一个 Java 的 SQL 映射框架，虽然在国内使用人数众多，但是也受到了许多吐槽。使用 MyBatis，你必须要忍受在 XML 里写 SQL 这种奇怪的操作，以及在众多 XML 与 Java 接口文件之间跳来跳去的麻烦，以及往 XML 中传递多个参数时的一坨坨 &lt;code&gt;@Param&lt;/code&gt; 注解(或者你使用 &lt;code&gt;Map&lt;/code&gt;？那就更糟了，连基本的类型校验都没有，参数名也容易写错)。甚至，在与 Kotlin 共存的时候，还会出现一些奇怪的问题，比如： &lt;a href=&quot;https://mp.weixin.qq.com/s?__biz=MzIzMTYzOTYzNA==&amp;amp;mid=2247483908&amp;amp;idx=1&amp;amp;sn=0c072a630198d4a23a7d3aec700c138b&amp;amp;chksm=e8a05d39dfd7d42f1494c5f0fcc0562112be6d8912e44fc51f17dee472f9ebdfaad7d930e3b1#rd&quot;&gt;Kotlin 遇到 MyBatis：到底是 Int 的错，还是 data class 的错？&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;这时，你可能想要一款专属于 Kotlin 的 ORM 框架。它可以充分利用 Kotlin 的各种优良特性，让我们写出更加 Kotlin 的代码。它应该是轻量级的，只需要添加依赖即可直接使用，不需要各种麻烦的配置文件。它的 SQL 最好可以自动生成，不需要像 MyBatis 那样每条 SQL 都自己写，但是也给我们保留精确控制 SQL 的能力，不至于像 Hibernate 那样难以进行 SQL 调优。&lt;/p&gt;
&lt;p&gt;如果你真的这么想的话，Ktorm 可能会适合你。Ktorm 是直接基于纯 JDBC 编写的高效简洁的 Kotlin ORM 框架，它提供了强类型而且灵活的 SQL DSL 和方便的序列 API，以减少我们操作数据库的重复劳动。当然，所有的 SQL 都是自动生成的。本文的目的就是对 Ktorm 进行介绍，帮助我们快速上手使用。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Kotlin" scheme="https://www.liuwj.me/tags/Kotlin/"/>
    
    <category term="Ktorm" scheme="https://www.liuwj.me/tags/Ktorm/"/>
    
  </entry>
  
  <entry>
    <title>找到编译器的 bug 是种怎样的体验？</title>
    <link href="https://www.liuwj.me/posts/kotlin-malformed-anonymous-class-names-in-interfaces/"/>
    <id>https://www.liuwj.me/posts/kotlin-malformed-anonymous-class-names-in-interfaces/</id>
    <published>2018-11-13T00:00:00.000Z</published>
    <updated>2026-03-01T02:56:55.951Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>本文来自我的知乎回答：<a href="https://www.zhihu.com/question/267143879/answer/530782765">找到编译器的bug是种怎样的体验？ - 知乎</a></p></blockquote><p>emmm…这个问题下面真的是大佬云集，萌新感到好忐忑…</p><p>前段时间在使用 Kotlin 开发一个 <a href="https://www.ktorm.org/zh-cn/">ORM 框架（广告慎入，Ktorm：专注于 Kotlin 的 ORM 框架）</a>，当时我的代码大概是这样的，定义了一个 <code>Foo</code> 接口，在这个接口里面写了个默认实现的 <code>bar()</code> 方法：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">Foo</span> &#123;</span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">bar</span><span class="params">()</span></span> &#123;</span><br><span class="line">        <span class="keyword">val</span> obj = <span class="keyword">object</span> : Any() &#123; &#125;</span><br><span class="line">        println(obj.javaClass.simpleName)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">(args: <span class="type">Array</span>&lt;<span class="type">String</span>&gt;)</span></span> &#123;</span><br><span class="line">    <span class="keyword">val</span> foo = <span class="keyword">object</span> : Foo &#123; &#125;</span><br><span class="line">    foo.bar()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>怎么样，看起来是不是稳如狗？然而，这段代码在运行的时候，却喷了我一脸异常：</p><span id="more"></span><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Exception in thread &quot;main&quot; java.lang.InternalError: Malformed class name</span><br><span class="line">    at java.lang.Class.getSimpleBinaryName(Class.java:1450)</span><br><span class="line">    at java.lang.Class.getSimpleName(Class.java:1309)</span><br><span class="line">    ...</span><br><span class="line">Caused by: java.lang.StringIndexOutOfBoundsException: String index out of range: -3</span><br><span class="line">    at java.lang.String.substring(String.java:1931)</span><br><span class="line">    at java.lang.Class.getSimpleBinaryName(Class.java:1448)</span><br><span class="line">    ... 4 more</span><br></pre></td></tr></table></figure><p>风中凌乱…我不就是想输出一下匿名对象的类名吗，这个 <code>InternalError</code> 是什么鬼…</p><p>惊讶之余，冷静下来好好理了理 Kotlin 生成 class 的规则，终于明白过来。</p><p>众所周知，在 Java 中，interface 里面是不能有方法实现的（Java 8 以前），然而，Kotlin 却可以直接在接口里面实现方法。我们知道，Kotlin 最终也是要编译成 Java 字节码，既然 Java 本身都不支持这种操作，Kotlin 是怎么做到的呢？</p><p>反编译 Kotlin 生成的字节码就可以看到，在编译出来的 <code>interface Foo</code> 中，<code>bar</code> 方法仍然是 abstract 的，并没有实现。但是，Kotlin 另外生成了一个 <code>Foo$DefaultImpls</code> 类，在这个类里面有一个静态方法，这个方法的签名是：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">bar</span><span class="params">(Foo $<span class="built_in">this</span>)</span></span><br></pre></td></tr></table></figure><p>这个方法里面的字节码，就是我们的 <code>bar()</code> 方法的默认实现了。这样，当一个 Kotlin 的类实现了 <code>Foo</code> 接口时，编译器就会自动为我们插入一个 <code>bar()</code> 方法的实现，这个实现只是简单调用了 <code>Foo$DefaultImpls</code> 里面的静态方法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">bar</span><span class="params">()</span> &#123;</span><br><span class="line">    DefaultImpls.bar(<span class="built_in">this</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这就是 Kotlin 中接口默认方法的实现原理。</p><p>然而这跟前面的 bug 又有什么关系…</p><p>我们回过头来看刚刚出 bug 的代码，可以看到一个 <code>object : Any() &#123; &#125;</code>，这应该会生成一个匿名内部类，看下编译结果，可以知道这个匿名内部类的名字是 <code>Foo$bar$obj$1</code>，这应该没什么特别的。</p><p>然后顺着异常栈去到 JDK 的 Class 类里面，看源码，可以看到报错的地方是这样的：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> String <span class="title function_">getSimpleBinaryName</span><span class="params">()</span> &#123;</span><br><span class="line">    Class&lt;?&gt; enclosingClass = getEnclosingClass();</span><br><span class="line">    <span class="keyword">if</span> (enclosingClass == <span class="literal">null</span>) <span class="comment">// top level class</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="comment">// Otherwise, strip the enclosing class&#x27; name</span></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> getName().substring(enclosingClass.getName().length());</span><br><span class="line">    &#125; <span class="keyword">catch</span> (IndexOutOfBoundsException ex) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">InternalError</span>(<span class="string">&quot;Malformed class name&quot;</span>, ex);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>额，好像找到原因了…</p><p>回到前面提到的匿名内部类 <code>Foo$bar$obj$1</code>，因为 <code>bar()</code> 方法是在 <code>Foo$DefaultImpls</code> 中实现的，所以对这个匿名类获取 <code>enclosingClass</code> 毫无疑问就是 <code>Foo$DefaultImpls</code> 了，然后在 substring 的时候就 GG 了…</p><p>最后，根据我粗浅的理解，应该可以得出结论，这个 bug 的根源是 Kotlin 在编译这个匿名内部类的时候生成的名字有误，如果生成的名字是 <code>Foo$DefaultImpls$bar$obj$1</code> 的话，bug 就不会发生。带着这个疑惑，我去 Kotlin issue 上面找了找，果然已经有人提出过这个问题，然而这个 issue 至今都是 open 状态，并没有得到解决，难道是这个 bug 会牵扯到其他地方？有兴趣的同学可以去看一看：<a href="https://youtrack.jetbrains.com/issue/KT-16727">Names for anonymous classes in interfaces are malformed : KT-16727</a></p><p>最终，bug 的原因是找到了，那在 Kotlin 修复这个 bug 之前应该怎么办呢？我们当然只能想办法绕过了，比如避免在接口的默认实现方法中使用匿名内部类，lambda 也不行，因为 Kotlin 的 lambda 也会编译成匿名类…</p><p>BTW，说到编译器的 bug，之前在使用 Java 8 的 lambda 的时候也遇到过一个，当时还在知乎吐槽了一下，这里也贴个链接，仅作记录：<a href="https://www.zhihu.com/question/53173886/answer/319791449">此处的lambda为什么不能用方法引用表示 - 知乎</a></p><p>以上</p>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;本文来自我的知乎回答：&lt;a href=&quot;https://www.zhihu.com/question/267143879/answer/530782765&quot;&gt;找到编译器的bug是种怎样的体验？ - 知乎&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;emmm…这个问题下面真的是大佬云集，萌新感到好忐忑…&lt;/p&gt;
&lt;p&gt;前段时间在使用 Kotlin 开发一个 &lt;a href=&quot;https://www.ktorm.org/zh-cn/&quot;&gt;ORM 框架（广告慎入，Ktorm：专注于 Kotlin 的 ORM 框架）&lt;/a&gt;，当时我的代码大概是这样的，定义了一个 &lt;code&gt;Foo&lt;/code&gt; 接口，在这个接口里面写了个默认实现的 &lt;code&gt;bar()&lt;/code&gt; 方法：&lt;/p&gt;
&lt;figure class=&quot;highlight kotlin&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;Foo&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;function&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;title&quot;&gt;bar&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt;&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;val&lt;/span&gt; obj = &lt;span class=&quot;keyword&quot;&gt;object&lt;/span&gt; : Any() &amp;#123; &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        println(obj.javaClass.simpleName)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;function&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;fun&lt;/span&gt; &lt;span class=&quot;title&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(args: &lt;span class=&quot;type&quot;&gt;Array&lt;/span&gt;&amp;lt;&lt;span class=&quot;type&quot;&gt;String&lt;/span&gt;&amp;gt;)&lt;/span&gt;&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;val&lt;/span&gt; foo = &lt;span class=&quot;keyword&quot;&gt;object&lt;/span&gt; : Foo &amp;#123; &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    foo.bar()&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;怎么样，看起来是不是稳如狗？然而，这段代码在运行的时候，却喷了我一脸异常：&lt;/p&gt;</summary>
    
    
    
    
    <category term="Kotlin" scheme="https://www.liuwj.me/tags/Kotlin/"/>
    
  </entry>
  
  <entry>
    <title>绕过 Java 编译器检查，在任何地方抛出受检异常</title>
    <link href="https://www.liuwj.me/posts/throwing-checked-exceptions-anywhere/"/>
    <id>https://www.liuwj.me/posts/throwing-checked-exceptions-anywhere/</id>
    <published>2017-12-16T00:00:00.000Z</published>
    <updated>2026-03-01T02:56:55.951Z</updated>
    
    <content type="html"><![CDATA[<p>这次我要写的内容也是一个黑科技，就是在实际工作中没卵用的那种。秉着实用主义至上的小伙伴们可以绕道，看了这篇文章也不会对您的工作有任何帮助。但是如果您喜欢抱着娱乐的精神钻研一下这些 tricks，我们就开始吧。</p><h2 id="java-异常简介"><a class="markdownIt-Anchor" href="#java-异常简介"></a> Java 异常简介</h2><p>众所周知，Java 的所有异常都派生自 Throwable 类，在继承结构上，从 Throwable 派生出了 Error 和 Exception 两大类。其中，Error 表示系统级别的严重程序错误，一般由 JVM 抛出，我们也不应该捕获这类异常，用户自定义的异常一般都派生自 Exception 类。</p><p>从是否被编译器强制检查一点，异常又可分为受检异常(Checked Exception)和未受检异常(Unchecked Exception)。未受检异常派生自 Error 或者 RuntimeException，表示不可恢复的程序错误，典型例子有 AssertionError、NullPointerException 等，编译器不会强制我们捕获这类异常。受检异常则是除了 Error/RuntimeException 之外，派生自 Throwable 或者 Exception 的其他异常，比如 IOException、SQLException 等。如果一个方法声明自己可能抛出受检异常，那么编译器会强制它的调用者必须使用 try-catch 捕获此异常，或者在自己的方法中加上 throws 声明将异常继续传播给外界。</p><p><img src="https://www.liuwj.me/files/in-post/java-exception-hierarchy.jpg" alt="" /></p><span id="more"></span><p>多年以来，Java 中受检异常的设计一直颇受争议，反对者认为，受检异常容易破坏方法声明的兼容性，会使代码的可读性降低，还增加开发的工作量等等。当然也有一些支持者，他们认为受检异常可以强迫程序员去思考，有助于他们写出更健壮的代码，可以参考王垠的文章「<a href="http://www.yinwang.org/blog-cn/2017/05/23/kotlin">Kotlin 和 Checked Exception</a>」。</p><p>在这里，我不想继续讨论受检异常到底是好还是坏，我只想以这个为切入点，随便讨论一点关于 Java 的八卦。</p><h2 id="checkedunchecked-who-cares"><a class="markdownIt-Anchor" href="#checkedunchecked-who-cares"></a> Checked/unchecked, who cares?</h2><p>上面讲过，如果一个方法可能抛出受检异常，就必须在方法上加上 throws 声明，也就是说，如果方法上没有 throws 声明，这个方法就不可能抛出受检异常吗？按照 Java 的语言规范，这当然不可能，否则受检异常不就名不符实了吗？</p><p>当然说话也不能这么绝对，作为一个程序员，我们在自认为不可能的地方找到的 bug 还少吗？Test first，我们先来看一段测试代码。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">SneakyThrows</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Throws an IOException sneakily.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">sneakyThrow</span><span class="params">()</span> <span class="comment">/* throws IOException */</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">testSneakyThrows</span><span class="params">(SneakyThrows sneaky)</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        sneaky.sneakyThrow();</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Throwable e) &#123;</span><br><span class="line">        <span class="keyword">if</span> (e <span class="keyword">instanceof</span> IOException) &#123;</span><br><span class="line">            System.out.println(sneaky.getClass().getSimpleName() + <span class="string">&quot; success!&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">AssertionError</span>(</span><br><span class="line">                sneaky.getClass().getSimpleName() + <span class="string">&quot; failed!&quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这是一个接口和一个测试方法。这个 SneakyThrows 接口自称它会抛出一个 IOException，然而它的方法上却没有 throws 声明。测试方法接受一个实现了 SneakyThrows 接口的对象，调用接口上的 sneakyThrow 方法，如果接口方法真的抛出了 IOException，则输出 success 字样，否则会抛出异常，测试失败。那么，聪明的你，有没有办法实现这样一个接口，使测试能够成功呢？</p><p>当然有，而且还不止一种方法！</p><h2 id="万能的-unsafe"><a class="markdownIt-Anchor" href="#万能的-unsafe"></a> 万能的 Unsafe</h2><p>在 Java 里，说到黑科技，大家总是会首先想到 sun.misc.Unsafe，这个类大量出现在 JDK 源码以及各种第三方类库的源码中，用于实现一些奇奇怪怪的功能。那么它能不能用来抛出一个受检异常呢？当然能，Unsafe 中刚好有一个 throwException 方法可以实现这个功能。可惜的是，获取 Unsafe 对象只有一个 Unsafe.getUnsafe() 方法，而这个方法中加了对调用者的检查，只有 jdk 中的类才能调用这个方法，否则将抛出 SecurityException。</p><p>但是我们还有反射，只要 Unsafe 对象是保存在一个 Java 的字段中，反射就可以直接拿到这个对象，无视访问权限以及安全检查。下面这段代码，首先通过反射得到了 Unsafe 对象，然后调用它的 throwException 方法，成功抛出了一个受检异常。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">UnsafeSneakyThrows</span> <span class="keyword">implements</span> <span class="title class_">SneakyThrows</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sneakyThrow</span><span class="params">()</span> &#123;</span><br><span class="line">        getUnsafe().throwException(<span class="keyword">new</span> <span class="title class_">IOException</span>());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    Unsafe <span class="title function_">getUnsafe</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">Field</span> <span class="variable">field</span> <span class="operator">=</span> Unsafe.class.getDeclaredField(<span class="string">&quot;theUnsafe&quot;</span>);</span><br><span class="line">            field.setAccessible(<span class="literal">true</span>);</span><br><span class="line">            <span class="keyword">return</span> (Unsafe) field.get(<span class="literal">null</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (NoSuchFieldException | IllegalAccessException e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>小伙伴们可以运行一下，这段代码完全做到我们之前认为不可能的事情，在一个没有 throws 声明的方法里抛出受检异常！这时，有心的小伙伴应该就能明白过来，所谓的受检不受检，其实只是一个编译器的魔法，JVM 是完全不关心的。这也是为什么基于 JVM 的其他语言，比如 Scala、Groovy 之类，完全抛弃了受检异常的设计，却能运行在 JVM 上，并且能和 Java 很好地兼容。另外，学过 C++ 的同学应该也知道，在 C++ 里面，异常并不像 Java 一样有一个共同的基类，C++ 的 throw 语句可以抛出任何东西，甚至直接抛出一个 int 之类的值类型，当然这是题外话。</p><p>通过 Unsafe，我们能玩的黑魔法还有很多，比如分配一段非托管的直接内存、绕过 Java 的类初始化机制直接创建一个未初始化的对象、通过偏移量直接修改任何对象内的字段、以及硬件级别的原子操作 CAS 等。正因如此，它的身影也在 JDK 源码和各种第三方类库中频繁出现。比如 concurrent 包中使用它实现了各种线程同步相关的工具类以及 AtomicXxx 系的各种无锁的原子操作；nio 使用它获得了直接操作裸内存的能力；netty 也因为它得以直接操作堆外内存，大大地提升了性能；各类序列化库也使用它绕过类初始化机制、以方便地实现反序列化。</p><p>然而，这种大杀器一般都会有很大的副作用，比如分配的非托管内存，如果不注意释放，很容易就造成内存泄露，其他的操作也往往是高危操作，正如其 Unsafe 的名字。有消息称，在 JDK9 中，随着新的模块系统的推出，真正杜绝了应用直接使用 Unsafe 类，到时这个黑魔法就不管用咯，可以看看 R 大在知乎的回答：「<a href="https://www.zhihu.com/question/29266773/answer/43757304">为什么JUC中大量使用了sun.misc.Unsafe 这个类，但官方却不建议开发者使用？ - RednaxelaFX的回答 - 知乎</a>」。</p><h2 id="坑爹的泛型"><a class="markdownIt-Anchor" href="#坑爹的泛型"></a> 坑爹的泛型</h2><p>泛型也是那些黑 Java 的人的主要喷点之一。在 Java 中，泛型也只是编译器的语法糖，JVM 中并不保留泛型的类型信息，其名曰「类型擦除」。JDK5 推出时，Java 已在各行各业广泛使用，采用类型擦除的泛型设计也是出于兼容性考虑，否则就要像 C# 一样，同时存在 System.Collections 和 System.Collections.Generic 两套集合框架。关于泛型的更多细节，也可以看看 R 大的文章「<a href="https://www.zhihu.com/question/34621277/answer/59440954">Reifiable generics与Type erasure generics各有怎样的优点与缺点？ - RednaxelaFX的回答 - 知乎</a>」。</p><p>然而，采用类型擦除除了大家都说烂了的那些坏处之外，还有一些不为人知的坑，比如下面这段代码就是。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">GenericSneakyThrows</span> <span class="keyword">implements</span> <span class="title class_">SneakyThrows</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sneakyThrow</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.&lt;RuntimeException&gt;sneakyThrow0(<span class="keyword">new</span> <span class="title class_">IOException</span>());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    &lt;X <span class="keyword">extends</span> <span class="title class_">Throwable</span>&gt; <span class="keyword">void</span> <span class="title function_">sneakyThrow0</span><span class="params">(Throwable e)</span> <span class="keyword">throws</span> X &#123;</span><br><span class="line">        <span class="keyword">throw</span> (X) e;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里定义了一个泛型声明为 <code>&lt;X extends Throwable&gt;</code> 方法，在内部将传入的 Throwable 强转为 X 之后再抛出，X 的具体类型取决于调用这个方法时指定的类型参数。在这里，只要将类型参数指定为 RuntimeException，然后不管传入一个什么异常，都可以直接抛出去，而不用 throws 声明。什么，你说为什么 IOException 可以强转成 RuntimeException？当然是因为类型擦除啊，由于类型擦除的存在，sneakyThrow0 在被调用的时候，X 在运行时实际上是擦除为 Throwable 类型，从 IOException 转成 Throwable 一点问题都不会有。</p><p>所以说，基于类型擦除的泛型，和受检异常的设计实际上是冲突的，如果说上面提到的 Unsafe 是内部 API，可以不允许外界调用，那么，在类型擦除和受检异常共存的 Java 里，永远也不可能解决这个问题。</p><p>顺便一提，在 JDK8 中，由于 lambda 的引入，改变了类型推断算法，上面代码中的类型参数其实是可以省略的，直接 <code>this.sneakyThrow0(new IOException())</code> 即可。</p><h2 id="evil-classnewinstance"><a class="markdownIt-Anchor" href="#evil-classnewinstance"></a> Evil Class.newInstance()</h2><p>在很多文章里面，都推荐大家在使用反射的时候，用 Constructor.newInstance() 代替 Class.newInstance() 创建对象，这是为什么呢？我们先看看下面这段代码。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ConstructorSneakyThrows</span> <span class="keyword">implements</span> <span class="title class_">SneakyThrows</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sneakyThrow</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            ConstructorThrowable.class.newInstance();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InstantiationException | IllegalAccessException e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ConstructorThrowable</span> &#123;</span><br><span class="line"></span><br><span class="line">    ConstructorThrowable() <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IOException</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>和上面两个例子一样，上面这段代码也可以抛出一个受检异常。我们首先写了一个 ConstructorThrowable 类，这个类有一个无参构造方法，在构造方法里面我们抛出了一个 IOException，因此在调用 Class.newInstance() 的时候就把这个异常传播了出去，从而绕过了编译器的检查。</p><p>那么，为什么 Constructor.newInstance() 就不会有这个问题呢？对比这两者的签名就可以发现，它的 throws 列表中多了一个 InvocationTargetException，在构造方法的执行过程中如果发生了异常，这个异常会被包装为 InvocationTargetException 再次抛出。</p><p>这两个方法明明有相同的作用，但是在异常方面却有微妙的差别。查看 JDK 源码我们可以看到，Class.newInstance() 底层其实就是先获取到 Constructor 对象，然后再把实际的操作代理给 Constructor.newInstance()。然而，在这个过程中，它捕获了 InvocationTargetException，然后使用 Unsafe 将其包装的 targetException 直接抛出。</p><p>为什么 Class.newInstance() 要多次一举呢？这其实是历史原因导致的。Java 的反射 API 是在 1.2 版本引入的，而 Class 类在之前就有了，如果在 Class.newInstance() 方法的 throws 声明中也加上 InvocationTargetException 的话，由于这个异常是受检异常，就会导致基于旧版 JDK 写的代码都不能通过编译。所以为了兼容性考虑，只能使用 Unsafe 来传播构造方法中产生的异常。这也是一个证明受检异常是设计失误的例子，即容易破坏兼容性、妨碍 API 的演化。</p><p>具体的细节，在 Class.newInstance() 的 Javadoc 中已经有介绍，Stack Overflow 上也有相关的讨论，「<a href="https://stackoverflow.com/questions/195321/why-is-class-newinstance-evil/">java - Why is Class.newInstance() “evil”? - Stack Overflow</a>」。</p><h2 id="废弃的-threadstop"><a class="markdownIt-Anchor" href="#废弃的-threadstop"></a> 废弃的 Thread.stop()</h2><p>在 JDK5 里面，Thread 类一口气废弃了好几个方法，它们就是 suspend/resume/stop 系列。当然，废弃归废弃，只要我们有充分的理由，也不是不能用它们。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ThreadStopSneakyThrows</span> <span class="keyword">implements</span> <span class="title class_">SneakyThrows</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sneakyThrow</span><span class="params">()</span> &#123;</span><br><span class="line">        Thread.currentThread().stop(<span class="keyword">new</span> <span class="title class_">IOException</span>());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如你所见，接收一个 Throwable 参数的 Thread.stop() 方法也可以用来实现 SneakyThrows，抛出一个受检异常。stop() 方法的作用是使指定线程产生一个异常，从而强行终止该线程的执行。在这里，我们使当前线程产生一个 IOException，以达到我们的目的。那么它为什么被废弃了呢？JDK 文档里面有详细的解释。</p><blockquote><p>This method is inherently unsafe. Stopping a thread with Thread.stop causes it to unlock all of the monitors that it has locked (as a natural consequence of the unchecked <code>ThreadDeath</code> exception propagating up the stack). If any of the objects previously protected by these monitors were in an inconsistent state, the damaged objects become visible to other threads, potentially resulting in arbitrary behavior. Many uses of <code>stop</code> should be replaced by code that simply modifies some variable to indicate that the target thread should stop running.  The target thread should check this variable regularly, and return from its run method in an orderly fashion if the variable indicates that it is to stop running. If the target thread waits for long periods (on a condition variable, for example), the <code>interrupt</code> method should be used to interrupt the wait.</p></blockquote><p>简单来说，如果一个线程已经获得了某个锁，正在执行某些互斥操作，stop() 方法会强行使这个线程失去锁，而此时，它的操作可能还没有执行完成，这就可能使变量处于不一致的状态，造成线程安全问题。</p><p>嘛，反正这个方法已经废弃了，再多说也没什么意义。值得一提的是，在 JDK8 里，带 Throwable 参数的 Thread.stop() 方法已经改成直接抛出 UnsupportedOperationException，完全不能使用了，只有无参数的重载版本还仍可使用（无参版本默认抛出 ThreadDeath）。所以上面那段代码，只有在 JDK7 及以下才有效，在 JDK8 中并不能通过测试。</p><h2 id="the-end"><a class="markdownIt-Anchor" href="#the-end"></a> The End</h2><p>好了，扯淡结束。本文简单介绍了 Java 中的受检异常和未受检异常的区别，指出受检异常只是编译器的魔法、JVM 底层并不关心，并给出了四种绕过编译器检查，在任何地方都可抛出受检异常的方法。在介绍这四种方法的时候，随便讲了一些与之相关的八卦。</p><p>我的观点是，学习一门编程语言，了解一下这门语言的八卦还是很有必要的。当你知道它都有那些缺点，你就会思考，为什么当初要这样设计，你就会明白，所有的缺点，其实都是工程上的妥协。</p><p>程序员啊，还是要保持这一颗八卦的心。</p><p>荆轲刺秦王。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;这次我要写的内容也是一个黑科技，就是在实际工作中没卵用的那种。秉着实用主义至上的小伙伴们可以绕道，看了这篇文章也不会对您的工作有任何帮助。但是如果您喜欢抱着娱乐的精神钻研一下这些 tricks，我们就开始吧。&lt;/p&gt;
&lt;h2 id=&quot;java-异常简介&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#java-异常简介&quot;&gt;&lt;/a&gt; Java 异常简介&lt;/h2&gt;
&lt;p&gt;众所周知，Java 的所有异常都派生自 Throwable 类，在继承结构上，从 Throwable 派生出了 Error 和 Exception 两大类。其中，Error 表示系统级别的严重程序错误，一般由 JVM 抛出，我们也不应该捕获这类异常，用户自定义的异常一般都派生自 Exception 类。&lt;/p&gt;
&lt;p&gt;从是否被编译器强制检查一点，异常又可分为受检异常(Checked Exception)和未受检异常(Unchecked Exception)。未受检异常派生自 Error 或者 RuntimeException，表示不可恢复的程序错误，典型例子有 AssertionError、NullPointerException 等，编译器不会强制我们捕获这类异常。受检异常则是除了 Error/RuntimeException 之外，派生自 Throwable 或者 Exception 的其他异常，比如 IOException、SQLException 等。如果一个方法声明自己可能抛出受检异常，那么编译器会强制它的调用者必须使用 try-catch 捕获此异常，或者在自己的方法中加上 throws 声明将异常继续传播给外界。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.liuwj.me/files/in-post/java-exception-hierarchy.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;</summary>
    
    
    
    
    <category term="Java" scheme="https://www.liuwj.me/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>Java Comparable 接口的一个小「坑」 - 关于 compareTo() 和 equals() 的一致性的思考</title>
    <link href="https://www.liuwj.me/posts/a-trap-of-comparable-interface/"/>
    <id>https://www.liuwj.me/posts/a-trap-of-comparable-interface/</id>
    <published>2017-05-20T00:00:00.000Z</published>
    <updated>2026-03-01T02:56:55.950Z</updated>
    
    <content type="html"><![CDATA[<p><code>Comparable</code> 是 Java 中非常常用的一个接口，但是其中也有一些值得深究的细节。</p><p>我们以「德州扑克」游戏的业务场景为例进行说明。「德州扑克」是一款风靡世界的扑克游戏，要实现这个游戏，首先要对系统进行建模，我们可能会写出这样的一段代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> <span class="title class_">PokerSuit</span> &#123;</span><br><span class="line">    SPADE, HEART, DIAMOND, CLUB</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PokerCard</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> number;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> PokerSuit suit;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">PokerCard</span><span class="params">(<span class="type">int</span> number, PokerSuit suit)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (number &lt; <span class="number">1</span> || number &gt; <span class="number">13</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;number&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="built_in">this</span>.number = number;</span><br><span class="line">        <span class="built_in">this</span>.suit = Objects.requireNonNull(suit);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getNumber</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> number;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> PokerSuit <span class="title function_">getSuit</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> suit;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><span id="more"></span><p><code>PokerCard</code> 是一个十分简单的模型类，但它足以描述游戏中的一张扑克牌。其中，number 表示扑克牌的点数，1 代表 A，11 ~ 13 代表 J ~ K；suit 表示扑克牌的花色，它是一个枚举类型；因为「德州扑克」中没有大王和小王，所以在这里不作考虑。</p><p>按照约定，如果我们需要把这个类用在基于哈希集合中，就必须重写它的 <code>hashCode</code> 和 <code>equals</code> 方法。这个容易，重写就是了：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">hashCode</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> Objects.hash(number, suit);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">equals</span><span class="params">(Object o)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!(o <span class="keyword">instanceof</span> PokerCard)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="type">PokerCard</span> <span class="variable">other</span> <span class="operator">=</span> (PokerCard) o;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">this</span>.number == other.number &amp;&amp; <span class="built_in">this</span>.suit == other.suit;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>另外，扑克牌之间需要比较大小，所以我们需要实现 <code>Comparable</code> 接口以支持比较操作。「德州扑克」比较牌的大小是单纯比较点数，忽略花色的，所以代码可能是这样：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PokerCard</span> <span class="keyword">implements</span> <span class="title class_">Comparable</span>&lt;PokerCard&gt; &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">compareTo</span><span class="params">(PokerCard other)</span> &#123;</span><br><span class="line">        <span class="comment">// because 1(A) is bigger than any other number</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">thisNum</span> <span class="operator">=</span> <span class="built_in">this</span>.number == <span class="number">1</span> ? <span class="number">14</span> : <span class="built_in">this</span>.number;</span><br><span class="line">        <span class="type">int</span> <span class="variable">otherNum</span> <span class="operator">=</span> other.number == <span class="number">1</span> ? <span class="number">14</span> : other.number;</span><br><span class="line">        <span class="keyword">return</span> thisNum - otherNum;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>到此为止，一切都是那么和谐，在设计上，这个类似乎没有任何问题，事实上，在大部分情况下，它也是完全可以正常工作的。</p><p>那么，现在我们需要表示一个「牌型」的概念，所谓「牌型」，在德州扑克里面，即是在玩家的手牌与桌面的公共牌中选取五张牌所组成的一个集合，在比牌时，「牌型」最大的玩家即可赢得奖池。在这个定义中，我们可以知道，「牌型」是一个集合，而且需要支持比较操作，因此我们可以让它实现 <code>Set</code> 和 <code>Comparable</code> 接口。在实际操作中，我们一般不会直接实现 <code>Set</code> 接口，而是选择继承 <code>AbstractSet</code> 类以减少代码量，因此，代码可能是这样的：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PokerCombination</span> </span><br><span class="line">        <span class="keyword">extends</span> <span class="title class_">AbstractSet</span>&lt;PokerCard&gt; <span class="keyword">implements</span> <span class="title class_">Comparable</span>&lt;PokerCombination&gt; &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> SortedSet&lt;PokerCard&gt; cards;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">PokerCombination</span><span class="params">(Collection&lt;PokerCard&gt; cards)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (cards == <span class="literal">null</span> || cards.size() != <span class="number">5</span>) &#123; </span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;cards&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="built_in">this</span>.cards = Collections.unmodifiableSortedSet(<span class="keyword">new</span> <span class="title class_">TreeSet</span>&lt;&gt;(cards));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Iterator&lt;PokerCard&gt; <span class="title function_">iterator</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> cards.iterator();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">size</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> cards.size();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">compareTo</span><span class="params">(PokerCombination o)</span> &#123;</span><br><span class="line">        <span class="comment">// compare the poker combinations</span></span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在这里，我们省略了 <code>compareTo</code> 方法的具体代码，但是，为了方便实现比较操作， 在<code>PokerCombination</code> 类的内部实现中，采用了 <code>SortedSet</code>，这是一个有序的集合，在其中的元素都会按照其自然顺序（即 <code>Comparable.compareTo</code> 方法定义的顺序）进行排序，<code>TreeSet</code> 是它的一个常见的实现类。</p><p>现在我们添加一个测试方法，测试这个类的行为是否正确：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testSize</span><span class="params">()</span> &#123;</span><br><span class="line">    Set&lt;PokerCard&gt; cards = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;();</span><br><span class="line">    cards.add(<span class="keyword">new</span> <span class="title class_">PokerCard</span>(<span class="number">1</span>, PokerSuit.CLUB));</span><br><span class="line">    cards.add(<span class="keyword">new</span> <span class="title class_">PokerCard</span>(<span class="number">1</span>, PokerSuit.HEART));</span><br><span class="line">    cards.add(<span class="keyword">new</span> <span class="title class_">PokerCard</span>(<span class="number">2</span>, PokerSuit.CLUB));</span><br><span class="line">    cards.add(<span class="keyword">new</span> <span class="title class_">PokerCard</span>(<span class="number">3</span>, PokerSuit.CLUB));</span><br><span class="line">    cards.add(<span class="keyword">new</span> <span class="title class_">PokerCard</span>(<span class="number">4</span>, PokerSuit.CLUB));</span><br><span class="line">    assertEquals(<span class="number">5</span>, cards.size());</span><br><span class="line"></span><br><span class="line">    <span class="type">PokerCombination</span> <span class="variable">combination</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">PokerCombination</span>(cards);</span><br><span class="line">    assertEquals(<span class="number">5</span>, combination.size());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个测试方法非常简单，它首先创建了一个集合，往里面添加了 5 张扑克牌，断言它的长度是 5，然后用这个集合构造了一个 <code>PokerCombination</code> 对象，再断言它的长度也是 5。就这样一个简单的测试，它几乎一定会运行成功，在很多人眼里，甚至都没有写这个它的必要。</p><p>然而，当你真的运行这个测试的时候，它却失败了<i class="emoji emoji-joy"></i>，错误信息如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">java.lang.AssertionError: </span><br><span class="line">Expected :5</span><br><span class="line">Actual   :4</span><br></pre></td></tr></table></figure><p>这是一个断言错误，发生在我们的第二次 <code>assertEquals</code> 调用时，我们期望 <code>PokerCombination</code> 的长度是 5，然而它却是 4。现在问题来了，为什么一个长度为 5 的集合，传入 <code>PokerCombination</code> 里面，却变成了 4 呢？这里面发生的事情，仅仅是将传入的集合复制到一个 <code>SortedSet</code> 里面而已。</p><p>我们尝试将 <code>SortedSet</code> 换成更为通用的 <code>Set</code>，将 <code>TreeSet</code> 换成 <code>HashSet</code>，发现测试能正常执行，但是换回 <code>SortedSet</code> 的时候，它又失败了，因此，问题一定与 <code>SortedSet</code> 有关。打开它的源码，查看 JavaDoc，我们看到了下面这段描述：</p><blockquote><p>Note that the ordering maintained by a sorted set (whether or not an explicit comparator is provided) must be <i>consistent with equals</i> if the sorted set is to correctly implement the <tt>Set</tt> interface.  (See the <tt>Comparable</tt> interface or <tt>Comparator</tt> interface for a precise definition of <i>consistent with equals</i>.)  This is so because the <tt>Set</tt> interface is defined in terms of the <tt>equals</tt> operation, but a sorted set performs all element comparisons using its <tt>compareTo</tt> (or <tt>compare</tt>) method, so two elements that are deemed equal by this method are, from the standpoint of the sorted set, equal.  The behavior of a sorted set <i>is</i> well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the <tt>Set</tt> interface.</blockquote><p>大概解释一下：如果要使 <code>SortedSet</code> 正确表现出与普通的 <code>Set</code> 相同的行为，那么它内部元素的顺序关系必须要「与 equals 一致（consistent with equals）」。这是因为 <code>Set</code> 使用 <code>equals</code> 方法判断元素的等同性，而 <code>SortedSet</code> 使用的是 <code>compareTo</code> 方法，即如果 <code>compareTo</code> 方法返回 0，<code>SortedSet</code> 就认为这两个元素是相等的。当 <code>compareTo</code> 与 <code>equals</code> 的一致性不能满足时，<code>SortedSet</code> 的行为就会违背 <code>Set</code> 接口的通用约定。</p><p>那么，什么叫「与 equals 一致（consistent with equals）」呢，<code>Comparable</code> 接口的 JavaDoc 里面有明确的定义。对于任意非空变量 x 和 y，满足 <code>(x.compareTo(y)==0) == (x.equals(y))</code>，即认为 <code>compareTo</code> 与 <code>equals</code> 一致。任何实现了 <code>Comparable</code>，但是并没有满足这个条件的类，都应该在自己的文档中明确注明这一点。</p><blockquote><p>It is strongly recommended, but <i>not</i> strictly required that <tt>(x.compareTo(y)==0) == (x.equals(y))</tt>.  Generally speaking, any class that implements the <tt>Comparable</tt> interface and violates this condition should clearly indicate this fact.  The recommended language is “Note: this class has a natural ordering that is inconsistent with equals.”</p></blockquote><p>然而，<code>Comparable</code> 接口对这种一致性的约定也只是「建议」，而不是必须严格执行的规则。当然，这是可以理解的，毕竟在现实世界中，这种不一致也是存在的。就比如我们现在这个业务场景，当我们比较两张扑克牌是否相同，需要同时考虑花色和点数，当我们只是比较它们的大小时，就会忽略它们的花色。因此当 <code>x.compareTo(y) == 0</code> 时，<code>x.equals(y)</code> 是不确定的。这就是 <code>compareTo</code> 与 <code>equals</code> 不一致的情况，这种不一致是合理的。</p><p>JDK 标准库中也有这种不一致的情况，比如 <code>BigDecimal</code> 类。如果你创建一个 <code>HashSet</code> 实例，并且添加 <code>new BigDecimal(&quot;1.0&quot;)</code> 和 <code>new BigDecimal(&quot;1.00&quot;)</code>，这个集合就将包含两个元素，因为新增到集合中的两个 <code>BigDecimal</code> 实例，通过 <code>equals</code> 方法来比较时是不相等的。然而，如果你把 <code>HashSet</code> 换成 <code>TreeSet</code>，集合中将只包含一个元素，因为这两个实例在使用 <code>compareTo</code> 方法来比较时是相等的。</p><p>在大部分情况下，如果我们的类并没有遵守这种一致性，一般也没有什么问题。但是如果要把这个类用在有序集合中的时候，可能就需要做一点设计上的权衡。在「德州扑克」这个场景中，我们可以在 <code>new TreeSet&lt;&gt;()</code> 的时候，额外提供一个与 <code>equals</code> 一致的 <code>Comparator</code>，使这个集合能够正确地遵守通用的约定。如果项目中使用到 <code>SortedSet</code> 的地方不止这一处，我们也可以妥协，提供一个与 <code>equals</code> 一致的 <code>compareTo</code> 方法，但是在真正需要比较牌的大小的时候，使用另外的 <code>compareIgnoreSuit</code> 方法，比如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PokerCard</span> <span class="keyword">implements</span> <span class="title class_">Comparable</span>&lt;PokerCard&gt; &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">compareTo</span><span class="params">(PokerCard other)</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">diff</span> <span class="operator">=</span> compareIgnoreSuit(other);</span><br><span class="line">        <span class="keyword">if</span> (diff != <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> diff;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">this</span>.suit.compareTo(other.suit);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">compareIgnoreSuit</span><span class="params">(PokerCard other)</span> &#123;</span><br><span class="line">        <span class="comment">// because 1(A) is bigger than any other number</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">thisNum</span> <span class="operator">=</span> <span class="built_in">this</span>.number == <span class="number">1</span> ? <span class="number">14</span> : <span class="built_in">this</span>.number;</span><br><span class="line">        <span class="type">int</span> <span class="variable">otherNum</span> <span class="operator">=</span> other.number == <span class="number">1</span> ? <span class="number">14</span> : other.number;</span><br><span class="line">        <span class="keyword">return</span> thisNum - otherNum;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样改过代码之后，之前的那个测试当然能通过，讨论也已基本结束，但是，我们的思考却不应该止步于此。正如题目所言，我把这个称为一个「坑」，但是在 <code>SortedSet</code> 的文档描述中，它却是一个 <i>well-defined feature</i>. 虽然文档中已经有了「免责声明」，但还是有不止一人曾经跳入这个「坑」里面，究其原因，恐怕与 <code>SortedSet</code> 继承了 <code>Set</code> 脱离不了干系。</p><p>继承了一个接口，却不遵守这个接口的约定，这实在让人难以理解。既然 <code>SortedSet</code> 无法使用 <code>equals</code> 来判断元素的等同性，就应该另立门户，成为一个独立的接口，而不是选择继承 <code>Set</code>。根据里氏替换原则(Liskov Substitution Principle LSP)，当我们把程序中的 <code>Set</code> 替换成其子接口 <code>SortedSet</code> 时，程序还应该能正常工作，<code>SortedSet</code> 并不能做到这一点，这正是其继承了 <code>Set</code>，却没有遵守 <code>Set</code> 的契约导致的。当然，标准库的设计者作出这个决策，应该也是权衡了利弊的结果，毕竟，直接继承 <code>Set</code> 可以方便地进行向上转型，方便使用者对 <code>SortedSet</code> 和其他的 <code>Set</code> 进行统一的处理。然而，如果我们把 <code>SortedSet</code> 独立为一个接口，也可以提供一个 <code>asSet</code> 视图方法，方便使用者在需要的时候将它视为一个 <code>Set</code>。因此我认为，选择让 <code>SortedSet</code> 继承 <code>Set</code>，是个弊大于利的决策。</p><p>以上只是对类库设计的一点拙见，班门弄斧，如果您有不同意见，欢迎讨论。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;code&gt;Comparable&lt;/code&gt; 是 Java 中非常常用的一个接口，但是其中也有一些值得深究的细节。&lt;/p&gt;
&lt;p&gt;我们以「德州扑克」游戏的业务场景为例进行说明。「德州扑克」是一款风靡世界的扑克游戏，要实现这个游戏，首先要对系统进行建模，我们可能会写出这样的一段代码：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;23&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;24&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;PokerSuit&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    SPADE, HEART, DIAMOND, CLUB&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;PokerCard&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; number;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;final&lt;/span&gt; PokerSuit suit;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;PokerCard&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; number, PokerSuit suit)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (number &amp;lt; &lt;span class=&quot;number&quot;&gt;1&lt;/span&gt; || number &amp;gt; &lt;span class=&quot;number&quot;&gt;13&lt;/span&gt;) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;title class_&quot;&gt;IllegalArgumentException&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;quot;number&amp;quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;built_in&quot;&gt;this&lt;/span&gt;.number = number;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;built_in&quot;&gt;this&lt;/span&gt;.suit = Objects.requireNonNull(suit);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;getNumber&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; number;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; PokerSuit &lt;span class=&quot;title function_&quot;&gt;getSuit&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; suit;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    
    <category term="Java" scheme="https://www.liuwj.me/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>逆天改命，Java 反射的黑科技</title>
    <link href="https://www.liuwj.me/posts/java-reflection-black-tech/"/>
    <id>https://www.liuwj.me/posts/java-reflection-black-tech/</id>
    <published>2016-09-03T00:00:00.000Z</published>
    <updated>2026-03-01T02:56:55.950Z</updated>
    
    <content type="html"><![CDATA[<blockquote class="blockquote-center">一个人的命运啊，当然要靠自我奋斗，但也要考虑到历史的进程。——长者。</blockquote>众所周知，反射是 Java 的一大利器，它可以做到许多看起来不可思议的事情，但是用得不好也会给我们的系统挖下许多坑。下面就介绍一个反射的黑科技，请充分理解并消化里面的知识<del>，并把这项技术用到实际的项目中去</del>。<p>在开始之前，我们先来念两句诗，代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">    recitePoems(<span class="literal">false</span>);</span><br><span class="line">    recitePoems(<span class="literal">true</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">recitePoems</span><span class="params">(Boolean b)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (b) &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;苟利国家生死以&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;岂因祸福避趋之&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><span id="more"></span><p>上面代码的输出是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">岂因祸福避趋之</span><br><span class="line">苟利国家生死以</span><br></pre></td></tr></table></figure><p>不对呀，反了反了，念诗都念错，姿势水平还是太低。怎么改呢，很简单，把两次 <code>recitePoems</code> 方法调用的参数调转过来就可以了？ naïve，本文的目的是介绍黑科技，当然不会用这种寻常的办法解决问题。</p><p>不卖关子了，直接上代码吧：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">doSomeMagic</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    <span class="type">Field</span> <span class="variable">modifiersField</span> <span class="operator">=</span> Field.class.getDeclaredField(<span class="string">&quot;modifiers&quot;</span>);</span><br><span class="line">    modifiersField.setAccessible(<span class="literal">true</span>);</span><br><span class="line"></span><br><span class="line">    <span class="type">Field</span> <span class="variable">trueField</span> <span class="operator">=</span> Boolean.class.getDeclaredField(<span class="string">&quot;TRUE&quot;</span>);</span><br><span class="line">    modifiersField.set(trueField, trueField.getModifiers() &amp; ~Modifier.FINAL);</span><br><span class="line"></span><br><span class="line">    <span class="type">Field</span> <span class="variable">falseField</span> <span class="operator">=</span> Boolean.class.getDeclaredField(<span class="string">&quot;FALSE&quot;</span>);</span><br><span class="line">    modifiersField.set(falseField, falseField.getModifiers() &amp; ~Modifier.FINAL);</span><br><span class="line"></span><br><span class="line">    <span class="type">Boolean</span> <span class="variable">trueValue</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br><span class="line">    trueField.set(<span class="literal">null</span>, <span class="literal">false</span>);</span><br><span class="line">    falseField.set(<span class="literal">null</span>, trueValue);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>接下来，只需要在 <code>main</code> 方法的开头调用这个名为 <code>doSomeMagic </code>的<del>膜法</del>方法就好了：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    doSomeMagic();</span><br><span class="line"></span><br><span class="line">    recitePoems(<span class="literal">false</span>);</span><br><span class="line">    recitePoems(<span class="literal">true</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>修改完毕之后，我们得到了期望的输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">苟利国家生死以</span><br><span class="line">岂因祸福避趋之</span><br></pre></td></tr></table></figure><p>那么，<code>doSomeMagic</code> 方法到底干了什么呢？很简单，它交换了 <code>Boolean.TRUE</code> 和 <code>Boolean.FALSE</code> 的值。为了能够重写它们的值，我们需要去掉它们的 final 修饰符，这就是 <code>xxxField.getModifiers() &amp; ~Modifier.FINAL</code> 的作用。</p><p>交换 <code>Boolean.TRUE</code> 和 <code>Boolean.FALSE </code>的值，为什么能够改变原代码的运行逻辑呢？我们看到，<code>recitePoems</code> 方法的形参是 <code>boolean</code> 的包装类型 <code>Boolean</code>，直接将 <code>true</code> 和 <code>false</code> 作为实参调用它时，将会发生自动装箱操作。而自动装箱操作是通过调用 <code>Boolean.valueOf()</code> 方法完成的，我们看看这个方法的源码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Returns a &#123;<span class="doctag">@code</span> Boolean&#125; instance representing the specified</span></span><br><span class="line"><span class="comment"> * &#123;<span class="doctag">@code</span> boolean&#125; value.  If the specified &#123;<span class="doctag">@code</span> boolean&#125; value</span></span><br><span class="line"><span class="comment"> * is &#123;<span class="doctag">@code</span> true&#125;, this method returns &#123;<span class="doctag">@code</span> Boolean.TRUE&#125;;</span></span><br><span class="line"><span class="comment"> * if it is &#123;<span class="doctag">@code</span> false&#125;, this method returns &#123;<span class="doctag">@code</span> Boolean.FALSE&#125;.</span></span><br><span class="line"><span class="comment"> * If a new &#123;<span class="doctag">@code</span> Boolean&#125; instance is not required, this method</span></span><br><span class="line"><span class="comment"> * should generally be used in preference to the constructor</span></span><br><span class="line"><span class="comment"> * &#123;<span class="doctag">@link</span> #Boolean(boolean)&#125;, as this method is likely to yield</span></span><br><span class="line"><span class="comment"> * significantly better space and time performance.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span>  b a boolean value.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> a &#123;<span class="doctag">@code</span> Boolean&#125; instance representing &#123;<span class="doctag">@code</span> b&#125;.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@since</span>  1.4</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Boolean <span class="title function_">valueOf</span><span class="params">(<span class="type">boolean</span> b)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> (b ? TRUE : FALSE);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到，<code>Boolean.valueOf()</code> 方法直接使用了 <code>Boolean.TRUE</code> 和 <code>Boolean.FALSE</code> 两个常量。这就是我们能做到如此“是非颠倒”的原因。</p><p>所以说，一个程序的命运啊，当然要靠自我的奋斗，但也要考虑历史的进程。你绝对不会知道，好好的一个 <code>true</code>，怎么就变成 <code>false</code> 了呢。</p><p>这篇文章讲了这么久也没别的，大概三件事：一个，去掉 <code>Boolean.TRUE</code> 和 <code>Boolean.FALSE</code> 的 final 修饰符；第二个，交换了它们的值；第三个，就是基本类型自动装箱的细节；如果说还有一点成绩，那就是在公司每个项目的 <code>main</code> 方法上调用了一下 <code>doSomeMagic</code> 方法，这对于被炒鱿鱼的命运有很大的关系。</p><p>很惭愧，就做了一点微小的工作，谢谢大家。</p>]]></content>
    
    
    <summary type="html">&lt;blockquote class=&quot;blockquote-center&quot;&gt;一个人的命运啊，当然要靠自我奋斗，但也要考虑到历史的进程。——长者。&lt;/blockquote&gt;
众所周知，反射是 Java 的一大利器，它可以做到许多看起来不可思议的事情，但是用得不好也会给我们的系统挖下许多坑。下面就介绍一个反射的黑科技，请充分理解并消化里面的知识&lt;del&gt;，并把这项技术用到实际的项目中去&lt;/del&gt;。
&lt;p&gt;在开始之前，我们先来念两句诗，代码如下：&lt;/p&gt;
&lt;figure class=&quot;highlight java&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(String[] args)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    recitePoems(&lt;span class=&quot;literal&quot;&gt;false&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    recitePoems(&lt;span class=&quot;literal&quot;&gt;true&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;recitePoems&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(Boolean b)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; (b) &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        System.out.println(&lt;span class=&quot;string&quot;&gt;&amp;quot;苟利国家生死以&amp;quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125; &lt;span class=&quot;keyword&quot;&gt;else&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;        System.out.println(&lt;span class=&quot;string&quot;&gt;&amp;quot;岂因祸福避趋之&amp;quot;&lt;/span&gt;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    
    <category term="Java" scheme="https://www.liuwj.me/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>「译」LINQ: Building an IQueryable Provider - Part IX: Removing redundant subqueries</title>
    <link href="https://www.liuwj.me/posts/linq-building-an-iqueryable-provider-part-ix/"/>
    <id>https://www.liuwj.me/posts/linq-building-an-iqueryable-provider-part-ix/</id>
    <published>2016-03-04T00:00:00.000Z</published>
    <updated>2026-03-01T02:56:55.950Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>英文原文是<a href="https://social.msdn.microsoft.com/profile/matt%20warren%20-%20msft/" title="Matt Warren">Matt Warren</a>发表在MSDN Blogs的系列文章之一，英文渣渣，翻译<strong>不供参考</strong>，请直接<a href="http://blogs.msdn.com/b/mattwar/archive/2008/01/16/linq-building-an-iqueryable-provider-part-ix.aspx">看原文</a>。</p></blockquote><p>现在写一篇新的文章的时间变得越来越长，似乎已经成了一个趋势了。要怪就怪电视编剧罢工吧，嗯。</p><h2 id="cleaning-up-the-mess"><a class="markdownIt-Anchor" href="#cleaning-up-the-mess"></a> Cleaning up the Mess</h2><p>我之前说过要把我们的查询翻译器不断累积下来的不必要的嵌套select表达式给清理掉。对于人类的大脑来说，简化一条SQL是一件很简单的事情。但是，对于计算机程序而言，保留这些无用的嵌套查询却更加容易，毕竟它们的语义是一样的。再者，我们希望少写一点代码的心情也无可厚非。</p><p>我们很容易就能从一条带有where子句的简单的查询中看出问题所在。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> c <span class="keyword">in</span> db.Customers</span><br><span class="line"><span class="keyword">where</span> c.Country == <span class="string">&quot;UK&quot;</span></span><br><span class="line"><span class="keyword">select</span> c;</span><br></pre></td></tr></table></figure><span id="more"></span><p>这条普通的查询将翻译为下面的SQL：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> t1.Country, t1.CustomerID, t1.ContactName, t1.Phone, t1.City</span><br><span class="line"><span class="keyword">FROM</span> (</span><br><span class="line">  <span class="keyword">SELECT</span> t0.Country, t0.CustomerID, t0.ContactName, t0.Phone, t0.City</span><br><span class="line">  <span class="keyword">FROM</span> Customers <span class="keyword">AS</span> t0</span><br><span class="line">) <span class="keyword">AS</span> t1</span><br><span class="line"><span class="keyword">WHERE</span> (t1.Country <span class="operator">=</span> <span class="string">&#x27;UK&#x27;</span>)</span><br></pre></td></tr></table></figure><p>为什么会有一个多余的SELECT？如果你理解了我们的翻译器的工作方式，并且知道这条LINQ查询的本质是什么的话，很容易就能知道答案。</p><p>这条LINQ查询的方法调用语法如下：</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">db.Customers.Where(c =&gt; c.Country == <span class="string">&quot;UK&quot;</span>).Select(c =&gt; c);</span><br></pre></td></tr></table></figure><p>这里面有两个LINQ查询操作符，<code>Where()</code>和<code>Select()</code>。我们在<code>QueryBinder</code>类中的翻译引擎将这两个方法调用翻译为两个独立的<code>SelectExpression</code>。</p><p>理想情况下，SQL查询应该如下所示：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> t0.Country, t0.CustomerID, t0.ContactName, t0.Phone, t0.City</span><br><span class="line"><span class="keyword">FROM</span> Customers <span class="keyword">AS</span> t0</span><br><span class="line"><span class="keyword">WHERE</span> (t0.Country <span class="operator">=</span> <span class="string">&#x27;UK&#x27;</span>)</span><br></pre></td></tr></table></figure><p>然而，这只是很简单的情况，随着操作符的增加，所生成的SQL会越来越槽糕。你觉得翻译器能够聪明到将多个where子句合并到一起吗？我确实没有添加任何代码。如果语言编译器能够帮我们完成这个工作就再好不过了，但是如果额外的where子句是在原查询已经创建完成之后添加到其中的又会如何呢？</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> query = </span><br><span class="line">    <span class="keyword">from</span> c <span class="keyword">in</span> db.Customers</span><br><span class="line">    <span class="keyword">where</span> c.Country == <span class="string">&quot;UK&quot;</span></span><br><span class="line">    <span class="keyword">select</span> c;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">query = <span class="keyword">from</span> c <span class="keyword">in</span> query</span><br><span class="line">        <span class="keyword">where</span> c.Phone == <span class="string">&quot;555-5555&quot;</span></span><br><span class="line">        <span class="keyword">select</span> c;</span><br></pre></td></tr></table></figure><p>这样翻译出来的SQL就变成了一个三层的庞然大物，可它又不能吃，要那么大干嘛。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> t2.CustomerID, t2.ContactName, t2.Phone, t2.City, t2.Country</span><br><span class="line"><span class="keyword">FROM</span> (</span><br><span class="line">  <span class="keyword">SELECT</span> t1.CustomerID, t1.ContactName, t1.Phone, t1.City, t1.Country</span><br><span class="line">  <span class="keyword">FROM</span> (</span><br><span class="line">    <span class="keyword">SELECT</span> t0.CustomerID, t0.ContactName, t0.Phone, t0.City, t0.Country</span><br><span class="line">    <span class="keyword">FROM</span> Customers <span class="keyword">AS</span> t0</span><br><span class="line">  ) <span class="keyword">AS</span> t1</span><br><span class="line">  <span class="keyword">WHERE</span> (t1.Country <span class="operator">=</span> <span class="string">&#x27;UK&#x27;</span>)</span><br><span class="line">) <span class="keyword">AS</span> t2</span><br><span class="line"><span class="keyword">WHERE</span> (t2.Phone <span class="operator">=</span> <span class="string">&#x27;555-5555&#x27;</span>)</span><br></pre></td></tr></table></figure><p>不仅如此，我只是添加了一个小小的投影，翻译器都会额外创建一个嵌套查询。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> query = </span><br><span class="line">    <span class="keyword">from</span> c <span class="keyword">in</span> db.Customers</span><br><span class="line">    <span class="keyword">where</span> c.Country == <span class="string">&quot;UK&quot;</span></span><br><span class="line">    <span class="keyword">select</span> c.CustomerID;</span><br></pre></td></tr></table></figure><p>翻译出来的SQL如下：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> t2.CustomerID</span><br><span class="line"><span class="keyword">FROM</span> (</span><br><span class="line">  <span class="keyword">SELECT</span> t1.CustomerID, t1.ContactName, t1.Phone, t1.City, t1.Country</span><br><span class="line">  <span class="keyword">FROM</span> (</span><br><span class="line">    <span class="keyword">SELECT</span> t0.CustomerID, t0.ContactName, t0.Phone, t0.City, t0.Country</span><br><span class="line">    <span class="keyword">FROM</span> Customers <span class="keyword">AS</span> t0</span><br><span class="line">  ) <span class="keyword">AS</span> t1</span><br><span class="line">  <span class="keyword">WHERE</span> (t1.Country <span class="operator">=</span> <span class="string">&#x27;UK&#x27;</span>)</span><br><span class="line">) <span class="keyword">AS</span> t2</span><br></pre></td></tr></table></figure><p>为什么内层的查询要把外层从来没有用到过的数据给select出来？但愿数据库的优化做得够好，不要传输那些用不上或者没有必要返回到客户端的数据。但是，如果我们的查询翻译器能够自己消除这些重复的嵌套，将其转换为像一个真正的人类写出来的简单的形式的话，不是更好吗？这样，我们就可以写像下面一样复杂的查询了：</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> query = <span class="keyword">from</span> c <span class="keyword">in</span> db.Customers</span><br><span class="line">            <span class="keyword">join</span> o <span class="keyword">in</span> db.Orders <span class="keyword">on</span> c.CustomerID <span class="keyword">equals</span> o.CustomerID</span><br><span class="line">            <span class="keyword">let</span> m = c.Phone</span><br><span class="line">            <span class="keyword">orderby</span> c.City</span><br><span class="line">            <span class="keyword">where</span> c.Country == <span class="string">&quot;UK&quot;</span></span><br><span class="line">            <span class="keyword">where</span> m != <span class="string">&quot;555-5555&quot;</span></span><br><span class="line">            <span class="keyword">select</span> <span class="keyword">new</span> &#123; c.City, c.ContactName &#125; <span class="keyword">into</span> x</span><br><span class="line">            <span class="keyword">where</span> x.City == <span class="string">&quot;London&quot;</span></span><br><span class="line">            <span class="keyword">select</span> x;</span><br></pre></td></tr></table></figure><p>我都不敢给你看这条查询会生成什么样的SQL了，因为我担心它会吓得你把电脑都关了。</p><p>接下来我就要告诉你我是如何挽起袖子写了些代码来拯救你的。其实也不是特别难。我原以为我们的代码会因为表达式树的不可变的特性而变得越来越复杂，因为order-by重写器似乎会很复杂，需要对表达式树进行的转换也越来越有趣。然而，我很惊喜地发现，我们清理多余的嵌套查询的逻辑实现起来却特别的简洁。</p>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;英文原文是&lt;a href=&quot;https://social.msdn.microsoft.com/profile/matt%20warren%20-%20msft/&quot; title=&quot;Matt Warren&quot;&gt;Matt Warren&lt;/a&gt;发表在MSDN Blogs的系列文章之一，英文渣渣，翻译&lt;strong&gt;不供参考&lt;/strong&gt;，请直接&lt;a href=&quot;http://blogs.msdn.com/b/mattwar/archive/2008/01/16/linq-building-an-iqueryable-provider-part-ix.aspx&quot;&gt;看原文&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;现在写一篇新的文章的时间变得越来越长，似乎已经成了一个趋势了。要怪就怪电视编剧罢工吧，嗯。&lt;/p&gt;
&lt;h2 id=&quot;cleaning-up-the-mess&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#cleaning-up-the-mess&quot;&gt;&lt;/a&gt; Cleaning up the Mess&lt;/h2&gt;
&lt;p&gt;我之前说过要把我们的查询翻译器不断累积下来的不必要的嵌套select表达式给清理掉。对于人类的大脑来说，简化一条SQL是一件很简单的事情。但是，对于计算机程序而言，保留这些无用的嵌套查询却更加容易，毕竟它们的语义是一样的。再者，我们希望少写一点代码的心情也无可厚非。&lt;/p&gt;
&lt;p&gt;我们很容易就能从一条带有where子句的简单的查询中看出问题所在。&lt;/p&gt;
&lt;figure class=&quot;highlight cs&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;from&lt;/span&gt; c &lt;span class=&quot;keyword&quot;&gt;in&lt;/span&gt; db.Customers&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;where&lt;/span&gt; c.Country == &lt;span class=&quot;string&quot;&gt;&amp;quot;UK&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;select&lt;/span&gt; c;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    
    <category term="C♯" scheme="https://www.liuwj.me/tags/C-Sharp/"/>
    
  </entry>
  
  <entry>
    <title>「译」LINQ: Building an IQueryable Provider - Part VIII: OrderBy</title>
    <link href="https://www.liuwj.me/posts/linq-building-an-iqueryable-provider-part-viii/"/>
    <id>https://www.liuwj.me/posts/linq-building-an-iqueryable-provider-part-viii/</id>
    <published>2016-02-27T00:00:00.000Z</published>
    <updated>2026-03-01T02:56:55.950Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>英文原文是<a href="https://social.msdn.microsoft.com/profile/matt%20warren%20-%20msft/" title="Matt Warren">Matt Warren</a>发表在MSDN Blogs的系列文章之一，英文渣渣，翻译<strong>不供参考</strong>，请直接<a href="http://blogs.msdn.com/b/mattwar/archive/2007/10/09/linq-building-an-iqueryable-provider-part-viii.aspx">看原文</a>。</p></blockquote><p>距离上篇文章，又已经过了几个星期。我感觉大家可能已经迫不及待想要看到下篇文章了。你们的提供程序本来应该已经完成，可以拿到外面去惊艳众人，但是现在却放在角落里吃灰。</p><h2 id="implementing-orderby"><a class="markdownIt-Anchor" href="#implementing-orderby"></a> Implementing OrderBy</h2><p>今天的话题是翻译order-by子句。幸运的是，进行排序操作的方式只有一种，那就是LINQ的排序操作符。但坏消息是，有四种不同的操作符。</p><p>使用查询的语法来写一条排序的查询是很简单的，只需一个子句就好。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> query = <span class="keyword">from</span> c <span class="keyword">in</span> db.Customers</span><br><span class="line">            <span class="keyword">orderby</span> c.Country, c.City</span><br><span class="line">            <span class="keyword">select</span> c;</span><br></pre></td></tr></table></figure><p>但是，将上面的查询转换为方法调用的形式的话，所涉及到的就不止是一个LINQ操作符了。</p><span id="more"></span><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> query = db.Customers.OrderBy(c =&gt; c.Country).ThenBy(c =&gt; c.City);</span><br></pre></td></tr></table></figure><p>事实上，对于每个特定的排序表达式，都有它对应的排序操作符。因此LINQ提供程序在翻译SQL的时候，就需要将这些独立的操作符转换到一个单独的子句中。翻译这个的代码会比翻译之前的那些操作符的代码复杂一点，主要是因为需要先将这些独立的操作符全部找出来，才能对它们进行操作。之前的那些操作符可以简单地在前一个查询的外面套一个新的select，它们要考虑的只是当前操作符的那些参数。而排序不是，它还要考虑到其他的操作符。</p><p>首先，我们需要一种用来表示order-by子句的方式。最简单的方式是在已有的<code>SelectExpression</code>中加上一个描述排序的属性。但是，因为每个排序表达式都有一个排序方向，升序或降序，所以我需要把这些方向也保存下来。</p><p>所以，我添加了下面的新的定义：</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="built_in">enum</span> OrderType &#123;</span><br><span class="line">    Ascending,</span><br><span class="line">    Descending</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">OrderExpression</span> &#123;</span><br><span class="line">    OrderType orderType;</span><br><span class="line">    Expression expression;</span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="title">OrderExpression</span>(<span class="params">OrderType orderType, Expression expression</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.orderType = orderType;</span><br><span class="line">        <span class="keyword">this</span>.expression = expression;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">internal</span> OrderType OrderType &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.orderType; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">internal</span> Expression Expression &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.expression; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个新的类型<code>OrderExpression</code>并不是一个真的<code>Expression</code>节点，因为我并不打算把它用在表达式树的任何位置，它只作为<code>SelectExpression</code>定义的一部分出现。因此<code>SelectExpression</code>也有一点小变化。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">SelectExpression</span> : <span class="title">Expression</span> &#123;</span><br><span class="line">    ...</span><br><span class="line">    ReadOnlyCollection&lt;OrderExpression&gt; orderBy;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="title">SelectExpression</span>(<span class="params"></span></span></span><br><span class="line"><span class="params"><span class="function">        Type type, <span class="built_in">string</span> <span class="keyword">alias</span>, IEnumerable&lt;ColumnDeclaration&gt; columns, </span></span></span><br><span class="line"><span class="params"><span class="function">        Expression <span class="keyword">from</span>, Expression <span class="keyword">where</span>, IEnumerable&lt;OrderExpression&gt; orderBy</span>)</span></span><br><span class="line"><span class="function">        : <span class="title">base</span>(<span class="params">(ExpressionType</span>)DbExpressionType.Select, type)</span> &#123;</span><br><span class="line">        ...</span><br><span class="line">        <span class="keyword">this</span>.orderBy = orderBy <span class="keyword">as</span> ReadOnlyCollection&lt;OrderExpression&gt;;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">this</span>.orderBy == <span class="literal">null</span> &amp;&amp; orderBy != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">this</span>.orderBy = <span class="keyword">new</span> List&lt;OrderExpression&gt;(orderBy).AsReadOnly();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">internal</span> ReadOnlyCollection&lt;OrderExpression&gt; OrderBy &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.orderBy; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当然，<code>DbExpressionVisitor</code>也需要一点小变化，以支持排序的功能。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">DbExpressionVisitor</span> : <span class="title">ExpressionVisitor</span> &#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">VisitSelect</span>(<span class="params">SelectExpression <span class="keyword">select</span></span>)</span> &#123;</span><br><span class="line">        Expression <span class="keyword">from</span> = <span class="keyword">this</span>.VisitSource(<span class="keyword">select</span>.From);</span><br><span class="line">        Expression <span class="keyword">where</span> = <span class="keyword">this</span>.Visit(<span class="keyword">select</span>.Where);</span><br><span class="line">        ReadOnlyCollection&lt;ColumnDeclaration&gt; columns = <span class="keyword">this</span>.VisitColumnDeclarations(<span class="keyword">select</span>.Columns);</span><br><span class="line">        ReadOnlyCollection&lt;OrderExpression&gt; orderBy = <span class="keyword">this</span>.VisitOrderBy(<span class="keyword">select</span>.OrderBy);</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">from</span> != <span class="keyword">select</span>.From || <span class="keyword">where</span> != <span class="keyword">select</span>.Where || columns != <span class="keyword">select</span>.Columns || orderBy != <span class="keyword">select</span>.OrderBy) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> SelectExpression(<span class="keyword">select</span>.Type, <span class="keyword">select</span>.Alias, columns, <span class="keyword">from</span>, <span class="keyword">where</span>, orderBy);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">select</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    ...</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> ReadOnlyCollection&lt;OrderExpression&gt; <span class="title">VisitOrderBy</span>(<span class="params">ReadOnlyCollection&lt;OrderExpression&gt; expressions</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (expressions != <span class="literal">null</span>) &#123;</span><br><span class="line">            List&lt;OrderExpression&gt; alternate = <span class="literal">null</span>;</span><br><span class="line">            <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>, n = expressions.Count; i &lt; n; i++) &#123;</span><br><span class="line">                OrderExpression expr = expressions[i];</span><br><span class="line">                Expression e = <span class="keyword">this</span>.Visit(expr.Expression);</span><br><span class="line">                <span class="keyword">if</span> (alternate == <span class="literal">null</span> &amp;&amp; e != expr.Expression) &#123;</span><br><span class="line">                    alternate = expressions.Take(i).ToList();</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (alternate != <span class="literal">null</span>) &#123;</span><br><span class="line">                    alternate.Add(<span class="keyword">new</span> OrderExpression(expr.OrderType, e));</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (alternate != <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> alternate.AsReadOnly();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> expressions;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>另外，我们还必须修改一下所有创建<code>SelectExpression</code>的地方，但这相对比较容易。</p><p>将order-by子句转换为文本也不是那么难。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">QueryFormatter</span> : <span class="title">DbExpressionVisitor</span> &#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitSelect</span>(<span class="params">SelectExpression <span class="keyword">select</span></span>)</span> &#123;</span><br><span class="line">        ...</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">select</span>.OrderBy != <span class="literal">null</span> &amp;&amp; <span class="keyword">select</span>.OrderBy.Count &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">this</span>.AppendNewLine(Indentation.Same);</span><br><span class="line">            sb.Append(<span class="string">&quot;ORDER BY &quot;</span>);</span><br><span class="line">            <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>, n = <span class="keyword">select</span>.OrderBy.Count; i &lt; n; i++) &#123;</span><br><span class="line">                OrderExpression exp = <span class="keyword">select</span>.OrderBy[i];</span><br><span class="line">                <span class="keyword">if</span> (i &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                    sb.Append(<span class="string">&quot;, &quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">this</span>.Visit(exp.Expression);</span><br><span class="line">                <span class="keyword">if</span> (exp.OrderType != OrderType.Ascending) &#123;</span><br><span class="line">                    sb.Append(<span class="string">&quot; DESC&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>麻烦的地方是<code>QueryBinder</code>，我们需要从这些方法调用表达式中读取需要的信息创建一个排序子句。我决定构造一个排序表达式的列表，然后把它们全部放到同一个<code>SelectExpression</code>中。因为<code>ThenBy</code>和<code>ThenByDescending</code>操作符必须跟在其他排序操作符后面，因此可以很容易自上而下遍历表达式树，将每个排序表达式添加到一个集合里面，直到访问到最后一个order-by子句（一个<code>OrderBy</code>或<code>OrderByDescending</code>操作符）为止。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">QueryBinder</span> : <span class="title">ExpressionVisitor</span> &#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitMethodCall</span>(<span class="params">MethodCallExpression m</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (m.Method.DeclaringType == <span class="keyword">typeof</span>(Queryable) ||</span><br><span class="line">            m.Method.DeclaringType == <span class="keyword">typeof</span>(Enumerable)) &#123;</span><br><span class="line">            ...</span><br><span class="line">            <span class="keyword">switch</span> (m.Method.Name) &#123;</span><br><span class="line">                <span class="keyword">case</span> <span class="string">&quot;OrderBy&quot;</span>:</span><br><span class="line">                    <span class="keyword">return</span> <span class="keyword">this</span>.BindOrderBy(m.Type, m.Arguments[<span class="number">0</span>], (LambdaExpression)StripQuotes(m.Arguments[<span class="number">1</span>]), OrderType.Ascending);</span><br><span class="line">                <span class="keyword">case</span> <span class="string">&quot;OrderByDescending&quot;</span>:</span><br><span class="line">                    <span class="keyword">return</span> <span class="keyword">this</span>.BindOrderBy(m.Type, m.Arguments[<span class="number">0</span>], (LambdaExpression)StripQuotes(m.Arguments[<span class="number">1</span>]), OrderType.Descending);</span><br><span class="line">                <span class="keyword">case</span> <span class="string">&quot;ThenBy&quot;</span>:</span><br><span class="line">                    <span class="keyword">return</span> <span class="keyword">this</span>.BindThenBy(m.Arguments[<span class="number">0</span>], (LambdaExpression)StripQuotes(m.Arguments[<span class="number">1</span>]), OrderType.Ascending);</span><br><span class="line">                <span class="keyword">case</span> <span class="string">&quot;ThenByDescending&quot;</span>:</span><br><span class="line">                    <span class="keyword">return</span> <span class="keyword">this</span>.BindThenBy(m.Arguments[<span class="number">0</span>], (LambdaExpression)StripQuotes(m.Arguments[<span class="number">1</span>]), OrderType.Descending);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    List&lt;OrderExpression&gt; thenBys;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">BindOrderBy</span>(<span class="params">Type resultType, Expression source, LambdaExpression orderSelector, OrderType orderType</span>)</span> &#123;</span><br><span class="line">        List&lt;OrderExpression&gt; myThenBys = <span class="keyword">this</span>.thenBys;</span><br><span class="line">        <span class="keyword">this</span>.thenBys = <span class="literal">null</span>;</span><br><span class="line">        ProjectionExpression projection = (ProjectionExpression)<span class="keyword">this</span>.Visit(source);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">this</span>.map[orderSelector.Parameters[<span class="number">0</span>]] = projection.Projector;</span><br><span class="line">        List&lt;OrderExpression&gt; orderings = <span class="keyword">new</span> List&lt;OrderExpression&gt;();</span><br><span class="line">        orderings.Add(<span class="keyword">new</span> OrderExpression(orderType, <span class="keyword">this</span>.Visit(orderSelector.Body)));</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (myThenBys != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="built_in">int</span> i = myThenBys.Count - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">                OrderExpression tb = myThenBys[i];</span><br><span class="line">                LambdaExpression lambda = (LambdaExpression)tb.Expression;</span><br><span class="line">                <span class="keyword">this</span>.map[lambda.Parameters[<span class="number">0</span>]] = projection.Projector;</span><br><span class="line">                orderings.Add(<span class="keyword">new</span> OrderExpression(tb.OrderType, <span class="keyword">this</span>.Visit(lambda.Body)));</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="built_in">string</span> <span class="keyword">alias</span> = <span class="keyword">this</span>.GetNextAlias();</span><br><span class="line">        ProjectedColumns pc = <span class="keyword">this</span>.ProjectColumns(projection.Projector, <span class="keyword">alias</span>, projection.Source.Alias);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> ProjectionExpression(</span><br><span class="line">            <span class="keyword">new</span> SelectExpression(resultType, <span class="keyword">alias</span>, pc.Columns, projection.Source, <span class="literal">null</span>, orderings.AsReadOnly()),</span><br><span class="line">            pc.Projector</span><br><span class="line">            );</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">BindThenBy</span>(<span class="params">Expression source, LambdaExpression orderSelector, OrderType orderType</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">this</span>.thenBys == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">this</span>.thenBys = <span class="keyword">new</span> List&lt;OrderExpression&gt;();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">this</span>.thenBys.Add(<span class="keyword">new</span> OrderExpression(orderType, orderSelector));</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.Visit(source);</span><br><span class="line">    &#125;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当<code>BindThenBy</code>方法（处理<code>ThenBy</code>和<code>ThenByDescending</code>）被调用时，我仅仅将此调用的参数追加的一个保存了then-by信息的列表中。我复用了<code>OrderExpression</code>类，用它来保存then-by信息，因为它们的结构是一样的。然后，当<code>BindOrderBy</code>方法被调用时，我们就得到了所有的排序表达式，构建一个单独的<code>SelectExpression</code>。注意，在我绑定then-by的时候，我逆序遍历了这个集合，因为then-by信息是从后往前添加进集合里的。</p><p>现在，一切都准备就绪了。</p><p>用下面这个查询测试一下吧：</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> query = <span class="keyword">from</span> c <span class="keyword">in</span> db.Customers</span><br><span class="line">            <span class="keyword">orderby</span> c.Country, c.City</span><br><span class="line">            <span class="keyword">select</span> c;</span><br></pre></td></tr></table></figure><p>它会被翻译为如下的SQL：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> t1.CustomerID, t1.ContactName, t1.Phone, t1.City, t1.Country</span><br><span class="line"><span class="keyword">FROM</span> (</span><br><span class="line">  <span class="keyword">SELECT</span> t0.CustomerID, t0.ContactName, t0.Phone, t0.City, t0.Country</span><br><span class="line">  <span class="keyword">FROM</span> Customers <span class="keyword">AS</span> t0</span><br><span class="line">) <span class="keyword">AS</span> t1</span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> t1.Country, t1.City</span><br></pre></td></tr></table></figure><p>哈哈，正如我所料。</p><p>不幸的是，事情还没有完。也许你知道我接下来要说什么，也许你会想，这家伙可能只是沉浸在其中不能自拔吧，这篇文章还有那么长。他不可能还没有完成，一定是搞错了，他一定是想骗我。搞得好像这是一个大难题一样，啊！我讨厌难题。</p><p>没错，上面的解决方案确实有问题。在这个例子中，排序似乎没有什么问题，翻译器翻译这个查询，服务器接收并运行它，返回一个排序好的结果。问题在其他潜在的地方。事实上，LINQ与SQL比起来，其排序的语法更为灵活自由。目前的情况是，只要稍微改一改上面的查询，翻译器就会生成一条无法在数据库上运行的非法的SQL。</p><p>LINQ允许你在任何你喜欢的地方放置排序表达式，而SQL的限制却比较严格。虽然会有一些特例，但是大部分情况下，我们都只能在最外层的select查询中写唯一的一个order-by子句。就比如我上面的例子，假如我将order-by子句的位置换到前面会怎么样？假如我在排序之后还使用了其他LINQ操作符的话会怎么样？</p><p>就好比下面这个查询。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> query = <span class="keyword">from</span> c <span class="keyword">in</span> db.Customers</span><br><span class="line">            <span class="keyword">orderby</span> c.City</span><br><span class="line">            <span class="keyword">where</span> c.Country == <span class="string">&quot;UK&quot;</span></span><br><span class="line">            <span class="keyword">select</span> c;</span><br></pre></td></tr></table></figure><p>它和之前的查询十分相似，只不过在orderby后面多了一个where子句。在SQL里面是不能这么写的。就算能这么写，我们的提供程序又会生成什么样的SQL呢？</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> t2.City, t2.Country, t2.CustomerID, t2.ContactName, t2.Phone</span><br><span class="line"><span class="keyword">FROM</span> (</span><br><span class="line">  <span class="keyword">SELECT</span> t1.City, t1.Country, t1.CustomerID, t1.ContactName, t1.Phone</span><br><span class="line">  <span class="keyword">FROM</span> (</span><br><span class="line">    <span class="keyword">SELECT</span> t0.City, t0.Country, t0.CustomerID, t0.ContactName, t0.Phone</span><br><span class="line">    <span class="keyword">FROM</span> Customers <span class="keyword">AS</span> t0</span><br><span class="line">  ) <span class="keyword">AS</span> t1</span><br><span class="line">  <span class="keyword">ORDER</span> <span class="keyword">BY</span> t1.City</span><br><span class="line">) <span class="keyword">AS</span> t2</span><br><span class="line"><span class="keyword">WHERE</span> (t2.Country <span class="operator">=</span> <span class="string">&#x27;UK&#x27;</span>)</span><br></pre></td></tr></table></figure><p>啊，这绝对是运行不了的。且不说这条SQL的文本长度可能会超出限制，单说order-by子句，它属于嵌套的子查询，这样子排序是不会发生的。至少，我们要做到，当用户这样子写的时候，不能抛出一个异常吧。</p><p>现在甚至在查询里面加一个简单的投影操作都会引发异常。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> query = <span class="keyword">from</span> c <span class="keyword">in</span> db.Customers</span><br><span class="line">            <span class="keyword">orderby</span> c.City</span><br><span class="line">            <span class="keyword">select</span> <span class="keyword">new</span> &#123; c.Country, c.City, c.ContactName &#125;;</span><br></pre></td></tr></table></figure><p>翻译上面的查询会出现同样的问题。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> t2.Country, t2.City, t2.ContactName</span><br><span class="line"><span class="keyword">FROM</span> (</span><br><span class="line">  <span class="keyword">SELECT</span> t1.City, t1.Country, t1.ContactName, t1.CustomerID, t1.Phone</span><br><span class="line">  <span class="keyword">FROM</span> (</span><br><span class="line">    <span class="keyword">SELECT</span> t0.City, t0.Country, t0.ContactName, t0.CustomerID, t0.Phone</span><br><span class="line">    <span class="keyword">FROM</span> Customers <span class="keyword">AS</span> t0</span><br><span class="line">  ) <span class="keyword">AS</span> t1</span><br><span class="line">  <span class="keyword">ORDER</span> <span class="keyword">BY</span> t1.City</span><br><span class="line">) <span class="keyword">AS</span> t2</span><br></pre></td></tr></table></figure><p>很明显，还有做一些额外的工作才能避免异常。问题是，什么工作？</p><p>（此处应有沉默）</p><p>当然，我早已有了你们期待的解决方案。我必须重建一下这颗查询树，使其遵守SQL排序的语法规则。这意味着将排序表达式从它们不该存在的地方提出来，放到它们应该在的地方去。</p><p>这件事做起来并不是那么容易。基于LINQ表达式节点的查询树是不可变的，这意味着我们不能修改它。但这并不是最难的地方，因为我们的访问器能够自动识别变化并且为我们创建一颗新的不可变的树。最难的地方是确保所有的表别名都能够正确匹配，并且处理好order-by子句引用到已经不存在的列的情况。</p><p>似乎重头戏现在也还没开始。</p><h2 id="reordering-for-sqls-sake"><a class="markdownIt-Anchor" href="#reordering-for-sqls-sake"></a> Reordering for SQL’s sake</h2><p>那么要如何实现呢？我另外写了一个访问器类，它负责移动树中的order-by子句。虽然我已经尽可能地简化它的代码，但是最终还是很复杂。其实我可以将这些重建树的逻辑集成到<code>QueryBinder</code>类中去的，但是这样会给已有的代码徒增许多复杂度。因此将这些逻辑提取出来会更好，这样就不会对其他代码造成影响。</p><p>看看代码吧。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> Move order-bys to the outermost select</span></span><br><span class="line"><span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">OrderByRewriter</span> : <span class="title">DbExpressionVisitor</span> &#123;</span><br><span class="line">    IEnumerable&lt;OrderExpression&gt; gatheredOrderings;</span><br><span class="line">    <span class="built_in">bool</span> isOuterMostSelect;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">OrderByRewriter</span>()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Expression <span class="title">Rewrite</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.isOuterMostSelect = <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.Visit(expression);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitSelect</span>(<span class="params">SelectExpression <span class="keyword">select</span></span>)</span> &#123;</span><br><span class="line">        <span class="built_in">bool</span> saveIsOuterMostSelect = <span class="keyword">this</span>.isOuterMostSelect;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">this</span>.isOuterMostSelect = <span class="literal">false</span>;</span><br><span class="line">            <span class="keyword">select</span> = (SelectExpression)<span class="keyword">base</span>.VisitSelect(<span class="keyword">select</span>);</span><br><span class="line">            <span class="built_in">bool</span> hasOrderBy = <span class="keyword">select</span>.OrderBy != <span class="literal">null</span> &amp;&amp; <span class="keyword">select</span>.OrderBy.Count &gt; <span class="number">0</span>;</span><br><span class="line">            <span class="keyword">if</span> (hasOrderBy) &#123;</span><br><span class="line">                <span class="keyword">this</span>.PrependOrderings(<span class="keyword">select</span>.OrderBy);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="built_in">bool</span> canHaveOrderBy = saveIsOuterMostSelect;</span><br><span class="line">            <span class="built_in">bool</span> canPassOnOrderings = !saveIsOuterMostSelect;</span><br><span class="line">            IEnumerable&lt;OrderExpression&gt; orderings = (canHaveOrderBy) ? <span class="keyword">this</span>.gatheredOrderings : <span class="literal">null</span>;</span><br><span class="line">            ReadOnlyCollection&lt;ColumnDeclaration&gt; columns = <span class="keyword">select</span>.Columns;</span><br><span class="line">            <span class="keyword">if</span> (<span class="keyword">this</span>.gatheredOrderings != <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="keyword">if</span> (canPassOnOrderings) &#123;</span><br><span class="line">                    HashSet&lt;<span class="built_in">string</span>&gt; producedAliases = <span class="keyword">new</span> AliasesProduced().Gather(<span class="keyword">select</span>.From);</span><br><span class="line">                    <span class="comment">// reproject order expressions using this select&#x27;s alias so the outer select will have properly formed expressions</span></span><br><span class="line">                    BindResult project = <span class="keyword">this</span>.RebindOrderings(<span class="keyword">this</span>.gatheredOrderings, <span class="keyword">select</span>.Alias, producedAliases, <span class="keyword">select</span>.Columns);</span><br><span class="line">                    <span class="keyword">this</span>.gatheredOrderings = project.Orderings;</span><br><span class="line">                    columns = project.Columns;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="keyword">this</span>.gatheredOrderings = <span class="literal">null</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (orderings != <span class="keyword">select</span>.OrderBy || columns != <span class="keyword">select</span>.Columns) &#123;</span><br><span class="line">                <span class="keyword">select</span> = <span class="keyword">new</span> SelectExpression(<span class="keyword">select</span>.Type, <span class="keyword">select</span>.Alias, columns, <span class="keyword">select</span>.From, <span class="keyword">select</span>.Where, orderings);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">select</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="keyword">this</span>.isOuterMostSelect = saveIsOuterMostSelect;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitJoin</span>(<span class="params">JoinExpression <span class="keyword">join</span></span>)</span> &#123;</span><br><span class="line">        <span class="comment">// make sure order by expressions lifted up from the left side are not lost</span></span><br><span class="line">        <span class="comment">// when visiting the right side</span></span><br><span class="line">        Expression left = <span class="keyword">this</span>.VisitSource(<span class="keyword">join</span>.Left);</span><br><span class="line">        IEnumerable&lt;OrderExpression&gt; leftOrders = <span class="keyword">this</span>.gatheredOrderings;</span><br><span class="line">        <span class="keyword">this</span>.gatheredOrderings = <span class="literal">null</span>; <span class="comment">// start on the right with a clean slate</span></span><br><span class="line">        Expression right = <span class="keyword">this</span>.VisitSource(<span class="keyword">join</span>.Right);</span><br><span class="line">        <span class="keyword">this</span>.PrependOrderings(leftOrders);</span><br><span class="line">        Expression condition = <span class="keyword">this</span>.Visit(<span class="keyword">join</span>.Condition);</span><br><span class="line">        <span class="keyword">if</span> (left != <span class="keyword">join</span>.Left || right != <span class="keyword">join</span>.Right || condition != <span class="keyword">join</span>.Condition) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> JoinExpression(<span class="keyword">join</span>.Type, <span class="keyword">join</span>.Join, left, right, condition);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">join</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> Add a sequence of order expressions to an accumulated list, prepending so as</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> to give precedence to the new expressions over any previous expressions</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;param name=&quot;newOrderings&quot;&gt;</span><span class="doctag">&lt;/param&gt;</span></span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">PrependOrderings</span>(<span class="params">IEnumerable&lt;OrderExpression&gt; newOrderings</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (newOrderings != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (<span class="keyword">this</span>.gatheredOrderings == <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="keyword">this</span>.gatheredOrderings = newOrderings;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">else</span> &#123;</span><br><span class="line">                List&lt;OrderExpression&gt; list = <span class="keyword">this</span>.gatheredOrderings <span class="keyword">as</span> List&lt;OrderExpression&gt;;</span><br><span class="line">                <span class="keyword">if</span> (list == <span class="literal">null</span>) &#123;</span><br><span class="line">                    <span class="keyword">this</span>.gatheredOrderings = list = <span class="keyword">new</span> List&lt;OrderExpression&gt;(<span class="keyword">this</span>.gatheredOrderings);</span><br><span class="line">                &#125;</span><br><span class="line">                list.InsertRange(<span class="number">0</span>, newOrderings);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">class</span> <span class="title">BindResult</span> &#123;</span><br><span class="line">        ReadOnlyCollection&lt;ColumnDeclaration&gt; columns;</span><br><span class="line">        ReadOnlyCollection&lt;OrderExpression&gt; orderings;</span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="title">BindResult</span>(<span class="params">IEnumerable&lt;ColumnDeclaration&gt; columns, IEnumerable&lt;OrderExpression&gt; orderings</span>)</span> &#123;</span><br><span class="line">            <span class="keyword">this</span>.columns = columns <span class="keyword">as</span> ReadOnlyCollection&lt;ColumnDeclaration&gt;;</span><br><span class="line">            <span class="keyword">if</span> (<span class="keyword">this</span>.columns == <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="keyword">this</span>.columns = <span class="keyword">new</span> List&lt;ColumnDeclaration&gt;(columns).AsReadOnly();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">this</span>.orderings = orderings <span class="keyword">as</span> ReadOnlyCollection&lt;OrderExpression&gt;;</span><br><span class="line">            <span class="keyword">if</span> (<span class="keyword">this</span>.orderings == <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="keyword">this</span>.orderings = <span class="keyword">new</span> List&lt;OrderExpression&gt;(orderings).AsReadOnly();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">public</span> ReadOnlyCollection&lt;ColumnDeclaration&gt; Columns &#123;</span><br><span class="line">            <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.columns; &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">public</span> ReadOnlyCollection&lt;OrderExpression&gt; Orderings &#123;</span><br><span class="line">            <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.orderings; &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> Rebind order expressions to reference a new alias and add to column declarations if necessary</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> BindResult <span class="title">RebindOrderings</span>(<span class="params">IEnumerable&lt;OrderExpression&gt; orderings, <span class="built_in">string</span> <span class="keyword">alias</span>, HashSet&lt;<span class="built_in">string</span>&gt; existingAliases, IEnumerable&lt;ColumnDeclaration&gt; existingColumns</span>)</span> &#123;</span><br><span class="line">        List&lt;ColumnDeclaration&gt; newColumns = <span class="literal">null</span>;</span><br><span class="line">        List&lt;OrderExpression&gt; newOrderings = <span class="keyword">new</span> List&lt;OrderExpression&gt;();</span><br><span class="line">        <span class="keyword">foreach</span> (OrderExpression ordering <span class="keyword">in</span> orderings) &#123;</span><br><span class="line">            Expression expr = ordering.Expression;</span><br><span class="line">            ColumnExpression column = expr <span class="keyword">as</span> ColumnExpression;</span><br><span class="line">            <span class="keyword">if</span> (column == <span class="literal">null</span> || (existingAliases != <span class="literal">null</span> &amp;&amp; existingAliases.Contains(column.Alias))) &#123;</span><br><span class="line">                <span class="comment">// check to see if a declared column already contains a similar expression</span></span><br><span class="line">                <span class="built_in">int</span> iOrdinal = <span class="number">0</span>;</span><br><span class="line">                <span class="keyword">foreach</span> (ColumnDeclaration decl <span class="keyword">in</span> existingColumns) &#123;</span><br><span class="line">                    ColumnExpression declColumn = decl.Expression <span class="keyword">as</span> ColumnExpression;</span><br><span class="line">                    <span class="keyword">if</span> (decl.Expression == ordering.Expression || </span><br><span class="line">                        (column != <span class="literal">null</span> &amp;&amp; declColumn != <span class="literal">null</span> &amp;&amp; column.Alias == declColumn.Alias &amp;&amp; column.Name == declColumn.Name)) &#123;</span><br><span class="line">                        <span class="comment">// found it, so make a reference to this column</span></span><br><span class="line">                        expr = <span class="keyword">new</span> ColumnExpression(column.Type, <span class="keyword">alias</span>, decl.Name, iOrdinal);</span><br><span class="line">                        <span class="keyword">break</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                    iOrdinal++;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// if not already projected, add a new column declaration for it</span></span><br><span class="line">                <span class="keyword">if</span> (expr == ordering.Expression) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (newColumns == <span class="literal">null</span>) &#123;</span><br><span class="line">                        newColumns = <span class="keyword">new</span> List&lt;ColumnDeclaration&gt;(existingColumns);</span><br><span class="line">                        existingColumns = newColumns;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="built_in">string</span> colName = column != <span class="literal">null</span> ? column.Name : <span class="string">&quot;c&quot;</span> + iOrdinal;</span><br><span class="line">                    newColumns.Add(<span class="keyword">new</span> ColumnDeclaration(colName, ordering.Expression));</span><br><span class="line">                    expr = <span class="keyword">new</span> ColumnExpression(expr.Type, <span class="keyword">alias</span>, colName, iOrdinal);</span><br><span class="line">                &#125;</span><br><span class="line">                newOrderings.Add(<span class="keyword">new</span> OrderExpression(ordering.OrderType, expr));</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> BindResult(existingColumns, newOrderings);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>代码好多:-)</p><p>主要的访问算法的工作方式如下。访问器自底向上遍历表达式树，它维护了一个增长的order-by表达式的集合。它与<code>QueryBinder</code>类刚好是相反的，<code>QueryBinder</code>自顶向下遍历表达式树，将then-by表达式添加到集合中。如果外层查询和内层查询都有order-by表达式的话，它们两个的表达式都不会丢失。外层查询的order-by表达式会放在内层查询的order-by表达式的前面。<code>VisitSelect</code>方法中调用了<code>PrependOrdering</code>方法，将当前order-by表达式添加到增长的列表的头部。</p><p>接下来我判断当前select节点是不是最外层的select节点，如果是，则它可以拥有order-by表达式，如果不是则不能拥有。如果我支持了TSQL的TOP子句的话，这个判断就有意思了。另外，我还要判断这个select节点是否可以向外层传递排序信息，如果它是内层节点的话，则可以。当然，如果我支持DISTINCT关键字的话，这里还会有更多的工作要做，原因在待会介绍<code>RebindOrdering</code>方法的时候就会明了。</p><p>当确定某个节点必须将它的order-by表达式传递到其外层节点时，这些order-by表达式必须要修改，以使其引用当前select节点的表别名，因为这些表达式原本引用的是内层查询的表别名。另外，如果order-by表达式中引用到了一些不存在于当前select节点的列投影中的列的话，我们还需要将这些列添加到投影中去，以便在外层查询中还能访问到它们。这整个过程称为重新绑定，这些逻辑都已经封装在<code>RebindOrdering</code>方法中。</p><p>现在回到之前说的那个问题，如果一个select节点使用了DISTINCT关键字，那么往投影中添加order-by表达式中引用到的列就会出错了。这些新添加的列会影响到distinct操作的结果。现在倒是不用担心这个问题，因为我们根本就不支持distinct，但是我们以后会支持，所以最好要提前考虑到这点。这就是LINQ to SQL在distinct或union操作中不支持排序的真正原因。</p><p>把前面提到的所有东西都加到代码里来，我们只需要修改一下<code>DBQueryProvider</code>类，让它调用新添加的访问器即可。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">DbQueryProvider</span> : <span class="title">QueryProvider</span> &#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="function"><span class="keyword">private</span> TranslateResult <span class="title">Translate</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        ProjectionExpression projection = expression <span class="keyword">as</span> ProjectionExpression;</span><br><span class="line">        <span class="keyword">if</span> (projection == <span class="literal">null</span>) &#123;</span><br><span class="line">            expression = Evaluator.PartialEval(expression, CanBeEvaluatedLocally);</span><br><span class="line">            expression = <span class="keyword">new</span> QueryBinder(<span class="keyword">this</span>).Bind(expression);</span><br><span class="line">            expression = <span class="keyword">new</span> OrderByRewriter().Rewrite(expression);</span><br><span class="line">            projection = (ProjectionExpression)expression;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="built_in">string</span> commandText = <span class="keyword">new</span> QueryFormatter().Format(projection.Source);</span><br><span class="line">        LambdaExpression projector = <span class="keyword">new</span> ProjectionBuilder().Build(projection.Projector, projection.Source.Alias);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> TranslateResult &#123; CommandText = commandText, Projector = projector &#125;;</span><br><span class="line">    &#125;</span><br><span class="line">    ...</span><br><span class="line">&#125; </span><br></pre></td></tr></table></figure><p>现在，执行下面这个不算太复杂的查询。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> query = <span class="keyword">from</span> c <span class="keyword">in</span> db.Customers</span><br><span class="line">            <span class="keyword">orderby</span> c.City</span><br><span class="line">            <span class="keyword">where</span> c.Country == <span class="string">&quot;UK&quot;</span></span><br><span class="line">            <span class="keyword">select</span> <span class="keyword">new</span> &#123; c.City, c.ContactName &#125;;</span><br></pre></td></tr></table></figure><p>翻译后得到如下SQL：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> t3.City, t3.ContactName</span><br><span class="line"><span class="keyword">FROM</span> (</span><br><span class="line">  <span class="keyword">SELECT</span> t2.City, t2.Country, t2.ContactName, t2.CustomerID, t2.Phone</span><br><span class="line">  <span class="keyword">FROM</span> (</span><br><span class="line">    <span class="keyword">SELECT</span> t1.City, t1.Country, t1.ContactName, t1.CustomerID, t1.Phone</span><br><span class="line">    <span class="keyword">FROM</span> (</span><br><span class="line">      <span class="keyword">SELECT</span> t0.City, t0.Country, t0.ContactName, t0.CustomerID, t0.Phone</span><br><span class="line">      <span class="keyword">FROM</span> Customers <span class="keyword">AS</span> t0</span><br><span class="line">    ) <span class="keyword">AS</span> t1</span><br><span class="line">  ) <span class="keyword">AS</span> t2</span><br><span class="line">  <span class="keyword">WHERE</span> (t2.Country <span class="operator">=</span> <span class="string">&#x27;UK&#x27;</span>)</span><br><span class="line">) <span class="keyword">AS</span> t3</span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> t3.City</span><br></pre></td></tr></table></figure><p>这可比之前生成的SQL好多了。</p><p>执行完成后，得到如下输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&#123; City = Cowes, ContactName = Helen Bennett &#125;</span><br><span class="line">&#123; City = London, ContactName = Simon Crowther &#125;</span><br><span class="line">&#123; City = London, ContactName = Hari Kumar &#125;</span><br><span class="line">&#123; City = London, ContactName = Thomas Hardy &#125;</span><br><span class="line">&#123; City = London, ContactName = Victoria Ashworth &#125;</span><br><span class="line">&#123; City = London, ContactName = Elizabeth Brown &#125;</span><br><span class="line">&#123; City = London, ContactName = Ann Devon &#125;</span><br></pre></td></tr></table></figure><p>好了，这就是排序的实现，至少也算是一个好的开始。</p><p>当然，如果我们能将那些不必要的子查询去掉的话就更好了。也许下次吧:-)</p><p><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Components.PostAttachments/00/05/38/61/88/Query8.zip">Query8.zip</a></p>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;英文原文是&lt;a href=&quot;https://social.msdn.microsoft.com/profile/matt%20warren%20-%20msft/&quot; title=&quot;Matt Warren&quot;&gt;Matt Warren&lt;/a&gt;发表在MSDN Blogs的系列文章之一，英文渣渣，翻译&lt;strong&gt;不供参考&lt;/strong&gt;，请直接&lt;a href=&quot;http://blogs.msdn.com/b/mattwar/archive/2007/10/09/linq-building-an-iqueryable-provider-part-viii.aspx&quot;&gt;看原文&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;距离上篇文章，又已经过了几个星期。我感觉大家可能已经迫不及待想要看到下篇文章了。你们的提供程序本来应该已经完成，可以拿到外面去惊艳众人，但是现在却放在角落里吃灰。&lt;/p&gt;
&lt;h2 id=&quot;implementing-orderby&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#implementing-orderby&quot;&gt;&lt;/a&gt; Implementing OrderBy&lt;/h2&gt;
&lt;p&gt;今天的话题是翻译order-by子句。幸运的是，进行排序操作的方式只有一种，那就是LINQ的排序操作符。但坏消息是，有四种不同的操作符。&lt;/p&gt;
&lt;p&gt;使用查询的语法来写一条排序的查询是很简单的，只需一个子句就好。&lt;/p&gt;
&lt;figure class=&quot;highlight cs&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;var&lt;/span&gt; query = &lt;span class=&quot;keyword&quot;&gt;from&lt;/span&gt; c &lt;span class=&quot;keyword&quot;&gt;in&lt;/span&gt; db.Customers&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;orderby&lt;/span&gt; c.Country, c.City&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;select&lt;/span&gt; c;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;但是，将上面的查询转换为方法调用的形式的话，所涉及到的就不止是一个LINQ操作符了。&lt;/p&gt;</summary>
    
    
    
    
    <category term="C♯" scheme="https://www.liuwj.me/tags/C-Sharp/"/>
    
  </entry>
  
  <entry>
    <title>「译」LINQ: Building an IQueryable Provider - Part VII: Join and SelectMany</title>
    <link href="https://www.liuwj.me/posts/linq-building-an-iqueryable-provider-part-vii/"/>
    <id>https://www.liuwj.me/posts/linq-building-an-iqueryable-provider-part-vii/</id>
    <published>2016-02-18T00:00:00.000Z</published>
    <updated>2026-03-01T02:56:55.950Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>英文原文是<a href="https://social.msdn.microsoft.com/profile/matt%20warren%20-%20msft/" title="Matt Warren">Matt Warren</a>发表在MSDN Blogs的系列文章之一，英文渣渣，翻译<strong>不供参考</strong>，请直接<a href="http://blogs.msdn.com/b/mattwar/archive/2007/09/04/linq-building-an-iqueryable-provider-part-vii.aspx">看原文</a>。</p></blockquote><p>从上篇文章到现在，已经有好几个星期没有更新了。希望在这段时间里面你们也有用自己的时间来探索如何构建自己的提供程序。我也一直在关注别人的各种各样的“LINQ to XXX”的项目，感觉都很不错。今天我将向你们介绍如何在我的提供程序中添加连接查询的功能，比起只支持select和where来，支持join将能提供更多有趣的用法。</p><h2 id="implementing-join"><a class="markdownIt-Anchor" href="#implementing-join"></a> Implementing Join</h2><p>在LINQ中有许多种不同的连接查询的写法。在C♯或者VB中，如果写了多个from子句，将会产生笛卡尔积的结果，但如果把一个子句的键和另一个子句的键匹配起来，所得到的就是一个连接查询。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> query = <span class="keyword">from</span> c <span class="keyword">in</span> db.Customers</span><br><span class="line">            <span class="keyword">from</span> o <span class="keyword">in</span> db.Orders</span><br><span class="line">            <span class="keyword">where</span> c.CustomerID == o.CustomerID</span><br><span class="line">            <span class="keyword">select</span> <span class="keyword">new</span> &#123; c.ContactName, o.OrderDate &#125;;</span><br></pre></td></tr></table></figure><span id="more"></span><p>当然，也可以使用显式的join子句。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> query = <span class="keyword">from</span> c <span class="keyword">in</span> db.Customers</span><br><span class="line">            <span class="keyword">join</span> o <span class="keyword">in</span> db.Orders <span class="keyword">on</span> c.CustomerID <span class="keyword">equals</span> o.CustomerID</span><br><span class="line">            <span class="keyword">select</span> <span class="keyword">new</span> &#123; c.ContactName, o.OrderDate &#125;;</span><br></pre></td></tr></table></figure><p>这两个查询会得到相同的结果，那么为什么做同一件事会有两种不同的方式呢？</p><p>原因有点复杂，但我会尝试解释清楚。显式连接要求我们指定两个匹配的键表达式，用数据库的术语来说，就是等值连接。而嵌套from子句具有更大的灵活性。显式连接具有如此限制的原因是，通过这种限制，使得LINQ to Objects的实现不必去分析和重写查询，进而使执行更加高效。好消息是，在数据库中用到的连接几乎都是等值连接。</p><p>并且，因为有了限制，所以显式查询的表达能力会比较低，因此实现起来会更加简单。在这篇文章里，两种连接方式我都会实现，但我会先完成显式连接，因为它的坑比较少。</p><p><code>Queryable.Join</code>方法的定义如下：</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="title">IQueryable</span>&lt;<span class="title">TResult</span>&gt; <span class="title">Join</span>&lt;<span class="title">TOuter</span>,<span class="title">TInner</span>,<span class="title">TKey</span>,<span class="title">TResult</span>&gt;(<span class="params"></span></span></span><br><span class="line"><span class="params"><span class="function">    <span class="keyword">this</span> IQueryable&lt;TOuter&gt; outer, </span></span></span><br><span class="line"><span class="params"><span class="function">    IEnumerable&lt;TInner&gt; inner, </span></span></span><br><span class="line"><span class="params"><span class="function">    Expression&lt;Func&lt;TOuter,TKey&gt;&gt; outerKeySelector, </span></span></span><br><span class="line"><span class="params"><span class="function">    Expression&lt;Func&lt;TInner,TKey&gt;&gt; innerKeySelector, </span></span></span><br><span class="line"><span class="params"><span class="function">    Expression&lt;Func&lt;TOuter,TInner,TResult&gt;&gt; resultSelector</span></span></span><br><span class="line"><span class="params"><span class="function"></span>)</span></span><br></pre></td></tr></table></figure><p>好多参数好多泛型！但是实际上理解起来也不是那么难。inner和outer是两个输入序列（join关键字两边的序列）；每个输入序列都有一个键选择器（on子句中equals关键字两边的表达式）；最后是一个产生连接查询的结果的表达式。最后这个resultSelector可能会使人迷惑，因为在C♯或VB的语法中看起来好像没有这个东西。但实际上是有的，在上面的例子中，它就是select表达式。在其他地方，它也有可能是一个编译器生成的投影，用来将数据传递到下一个查询操作中。</p><p>没关系，直接开干吧。实际上，我早已万事俱备，只欠东风了。这个东风就是表示连接的新的节点。</p><p>现在在代码中加上这个节点。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="built_in">enum</span> DbExpressionType &#123;</span><br><span class="line">    Table = <span class="number">1000</span>, <span class="comment">// make sure these don&#x27;t overlap with ExpressionType</span></span><br><span class="line">    Column,</span><br><span class="line">    Select,</span><br><span class="line">    Projection,</span><br><span class="line">    Join</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我在枚举中加上了新的节点类型“Join”，然后实现一个<code>JoinExpression</code>类。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="built_in">enum</span> JoinType &#123;</span><br><span class="line">    CrossJoin,</span><br><span class="line">    InnerJoin,</span><br><span class="line">    CrossApply,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">JoinExpression</span> : <span class="title">Expression</span> &#123;</span><br><span class="line">    JoinType joinType;</span><br><span class="line">    Expression left;</span><br><span class="line">    Expression right;</span><br><span class="line">    Expression condition;</span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="title">JoinExpression</span>(<span class="params">Type type, JoinType joinType, Expression left, Expression right, Expression condition</span>)</span></span><br><span class="line"><span class="function">        : <span class="title">base</span>(<span class="params">(ExpressionType</span>)DbExpressionType.Join, type)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.joinType = joinType;</span><br><span class="line">        <span class="keyword">this</span>.left = left;</span><br><span class="line">        <span class="keyword">this</span>.right = right;</span><br><span class="line">        <span class="keyword">this</span>.condition = condition;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">internal</span> JoinType Join &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.joinType; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">internal</span> Expression Left &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.left; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">internal</span> Expression Right &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.right; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">internal</span> <span class="keyword">new</span> Expression Condition &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.condition; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我还定义了一个<code>JoinType</code>的枚举，里面是我待会要用到的连接类型。<code>CrossApply</code>是SQL Server中独有的连接类型。现在先忽略它，在实现等值连接的使用用不到它。实际上，现在只需要<code>InnerJoin</code>，另外两个在后面才会用到。我说过，显式连接是比较简单的。</p><p>那么外连接呢？这个我们会在后面的文章中讨论:-)</p><p>现在多了个<code>JoinExpression</code>，所以<code>DbExpressionVisitor</code>得改一改。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">DbExpressionVisitor</span> : <span class="title">ExpressionVisitor</span> &#123;</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">Visit</span>(<span class="params">Expression exp</span>)</span> &#123;</span><br><span class="line">        ...</span><br><span class="line">        <span class="keyword">switch</span> ((DbExpressionType)exp.NodeType) &#123;</span><br><span class="line">            ...</span><br><span class="line">            <span class="keyword">case</span> DbExpressionType.Join:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.VisitJoin((JoinExpression)exp);</span><br><span class="line">            ...</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    ...</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">VisitJoin</span>(<span class="params">JoinExpression <span class="keyword">join</span></span>)</span> &#123;</span><br><span class="line">        Expression left = <span class="keyword">this</span>.Visit(<span class="keyword">join</span>.Left);</span><br><span class="line">        Expression right = <span class="keyword">this</span>.Visit(<span class="keyword">join</span>.Right);</span><br><span class="line">        Expression condition = <span class="keyword">this</span>.Visit(<span class="keyword">join</span>.Condition);</span><br><span class="line">        <span class="keyword">if</span> (left != <span class="keyword">join</span>.Left || right != <span class="keyword">join</span>.Right || condition != <span class="keyword">join</span>.Condition) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> JoinExpression(<span class="keyword">join</span>.Type, <span class="keyword">join</span>.Join, left, right, condition);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">join</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>还挺不错的。现在是改改<code>QueryFormatter</code>，以支持新添加的节点。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">QueryFormatter</span> : <span class="title">DbExpressionVisitor</span> &#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitSource</span>(<span class="params">Expression source</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">switch</span> ((DbExpressionType)source.NodeType) &#123;</span><br><span class="line">            ...</span><br><span class="line">            <span class="keyword">case</span> DbExpressionType.Join:</span><br><span class="line">                <span class="keyword">this</span>.VisitJoin((JoinExpression)source);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            ...</span><br><span class="line">        &#125;</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitJoin</span>(<span class="params">JoinExpression <span class="keyword">join</span></span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.VisitSource(<span class="keyword">join</span>.Left);</span><br><span class="line">        <span class="keyword">this</span>.AppendNewLine(Indentation.Same);</span><br><span class="line">        <span class="keyword">switch</span> (<span class="keyword">join</span>.Join) &#123;</span><br><span class="line">            <span class="keyword">case</span> JoinType.CrossJoin:</span><br><span class="line">                sb.Append(<span class="string">&quot;CROSS JOIN &quot;</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> JoinType.InnerJoin:</span><br><span class="line">                sb.Append(<span class="string">&quot;INNER JOIN &quot;</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> JoinType.CrossApply:</span><br><span class="line">                sb.Append(<span class="string">&quot;CROSS APPLY &quot;</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">this</span>.VisitSource(<span class="keyword">join</span>.Right);</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">join</span>.Condition != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">this</span>.AppendNewLine(Indentation.Inner);</span><br><span class="line">            sb.Append(<span class="string">&quot;ON &quot;</span>);</span><br><span class="line">            <span class="keyword">this</span>.Visit(<span class="keyword">join</span>.Condition);</span><br><span class="line">            <span class="keyword">this</span>.AppendNewLine(Indentation.Outer);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">join</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>现在的想法是，<code>JoinExpression</code>与其他查询源表达式（比如<code>SelectExpression</code>和<code>TableExpression</code>）在表达式树中是处于同一级别的，能出现它们的地方就能出现<code>JoinExpression</code>。因此我修改了<code>VisitSource</code>方法以使它支持连接，还增加了一个新的方法<code>VisitJoin</code>。</p><p>当然，如果不能将调用了<code>Queryable.Join</code>方法的表达式节点转换为我的<code>JoinExpression</code>的话，前面的工作就等于白费了。我需要在<code>QueryBinder</code>中添加一个方法，就像<code>BindSelect</code>和<code>BindWhere</code>方法一样。这就是实现显式连接的主要代码，因为有了之前实现其他操作符的时候写的代码的支持，所以实现显式连接显得特别简单。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">QueryBinder</span> : <span class="title">ExpressionVisitor</span> &#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitMethodCall</span>(<span class="params">MethodCallExpression m</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (m.Method.DeclaringType == <span class="keyword">typeof</span>(Queryable) ||</span><br><span class="line">            m.Method.DeclaringType == <span class="keyword">typeof</span>(Enumerable)) &#123;</span><br><span class="line">            <span class="keyword">switch</span> (m.Method.Name) &#123;</span><br><span class="line">                ...</span><br><span class="line">                <span class="keyword">case</span> <span class="string">&quot;Join&quot;</span>:</span><br><span class="line">                    <span class="keyword">return</span> <span class="keyword">this</span>.BindJoin(</span><br><span class="line">                        m.Type, m.Arguments[<span class="number">0</span>], m.Arguments[<span class="number">1</span>],</span><br><span class="line">                        (LambdaExpression)StripQuotes(m.Arguments[<span class="number">2</span>]),</span><br><span class="line">                        (LambdaExpression)StripQuotes(m.Arguments[<span class="number">3</span>]),</span><br><span class="line">                        (LambdaExpression)StripQuotes(m.Arguments[<span class="number">4</span>])</span><br><span class="line">                    );</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">    ...</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">BindJoin</span>(<span class="params">Type resultType, Expression outerSource, Expression innerSource, LambdaExpression outerKey, LambdaExpression innerKey, LambdaExpression resultSelector</span>)</span> &#123;</span><br><span class="line">        ProjectionExpression outerProjection = (ProjectionExpression)<span class="keyword">this</span>.Visit(outerSource);</span><br><span class="line">        ProjectionExpression innerProjection = (ProjectionExpression)<span class="keyword">this</span>.Visit(innerSource);</span><br><span class="line">        <span class="keyword">this</span>.map[outerKey.Parameters[<span class="number">0</span>]] = outerProjection.Projector;</span><br><span class="line">        Expression outerKeyExpr = <span class="keyword">this</span>.Visit(outerKey.Body);</span><br><span class="line">        <span class="keyword">this</span>.map[innerKey.Parameters[<span class="number">0</span>]] = innerProjection.Projector;</span><br><span class="line">        Expression innerKeyExpr = <span class="keyword">this</span>.Visit(innerKey.Body);</span><br><span class="line">        <span class="keyword">this</span>.map[resultSelector.Parameters[<span class="number">0</span>]] = outerProjection.Projector;</span><br><span class="line">        <span class="keyword">this</span>.map[resultSelector.Parameters[<span class="number">1</span>]] = innerProjection.Projector;</span><br><span class="line">        Expression resultExpr = <span class="keyword">this</span>.Visit(resultSelector.Body);</span><br><span class="line">        JoinExpression <span class="keyword">join</span> = <span class="keyword">new</span> JoinExpression(resultType, JoinType.InnerJoin, outerProjection.Source, innerProjection.Source, Expression.Equal(outerKeyExpr, innerKeyExpr));</span><br><span class="line">        <span class="built_in">string</span> <span class="keyword">alias</span> = <span class="keyword">this</span>.GetNextAlias();</span><br><span class="line">        ProjectedColumns pc = <span class="keyword">this</span>.ProjectColumns(resultExpr, <span class="keyword">alias</span>, outerProjection.Source.Alias, innerProjection.Source.Alias);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> ProjectionExpression(</span><br><span class="line">            <span class="keyword">new</span> SelectExpression(resultType, <span class="keyword">alias</span>, pc.Columns, <span class="keyword">join</span>, <span class="literal">null</span>),</span><br><span class="line">            pc.Projector</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>一眼看过去，<code>BindJoin</code>方法里面的实现与其他两个操作符的实现几乎是一样的。我首先将传入的两个源转换为两个不同的源的投影。我将这两个源的投影的投影器保存在全局的map对象中，在待会翻译两个键表达式的时候用来替换掉参数引用。最后在对结果表达式作同样的操作，不同的是结果表达式可以同时访问到两个源投影，而不仅仅是一个。</p><p>当所有的输入表达式都翻译完成之后，我就拥有了表示这个连接查询的足够的信息，因此已经可以创建<code>JoinExpression</code>了。然后再创建一个<code>SelectExpression</code>，将其包装起来，这里就需要调用<code>ProjectColumns</code>方法以产生一个数据列的列表以供<code>SelectExpression</code>使用。注意，现在<code>ProjectColumns</code>方法有一点小变化，它现在允许指定多个已存在的表别名。这点很重要，因为在连接操作里面，结果表达式很有可能会引用两个表别名。</p><p>搞定，所有东西都做完了。应该可以支持显式连接了。</p><p>试一试吧。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> query = <span class="keyword">from</span> c <span class="keyword">in</span> db.Customers</span><br><span class="line">            <span class="keyword">where</span> c.CustomerID == <span class="string">&quot;ALFKI&quot;</span></span><br><span class="line">            <span class="keyword">join</span> o <span class="keyword">in</span> db.Orders <span class="keyword">on</span> c.CustomerID <span class="keyword">equals</span> o.CustomerID</span><br><span class="line">            <span class="keyword">select</span> <span class="keyword">new</span> &#123; c.ContactName, o.OrderDate &#125;;</span><br><span class="line"></span><br><span class="line">Console.WriteLine(query);</span><br><span class="line"></span><br><span class="line"><span class="keyword">foreach</span> (<span class="keyword">var</span> item <span class="keyword">in</span> query) &#123;</span><br><span class="line">    Console.WriteLine(item);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>执行上面的代码，产生如下输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">SELECT t2.ContactName, t4.OrderDate</span><br><span class="line">FROM (</span><br><span class="line">  SELECT t1.CustomerID, t1.ContactName, t1.Phone, t1.City, t1.Country</span><br><span class="line">  FROM (</span><br><span class="line">    SELECT t0.CustomerID, t0.ContactName, t0.Phone, t0.City, t0.Country</span><br><span class="line">    FROM Customers AS t0</span><br><span class="line">  ) AS t1</span><br><span class="line">  WHERE (t1.CustomerID = &#x27;ALFKI&#x27;)</span><br><span class="line">) AS t2</span><br><span class="line">INNER JOIN (</span><br><span class="line">  SELECT t3.OrderID, t3.CustomerID, t3.OrderDate</span><br><span class="line">  FROM Orders AS t3</span><br><span class="line">) AS t4</span><br><span class="line">  ON (t2.CustomerID = t4.CustomerID)</span><br><span class="line">&#123; ContactName = Maria Anders, OrderDate = 8/25/1997 12:00:00 AM &#125;</span><br><span class="line">&#123; ContactName = Maria Anders, OrderDate = 10/3/1997 12:00:00 AM &#125;</span><br><span class="line">&#123; ContactName = Maria Anders, OrderDate = 10/13/1997 12:00:00 AM &#125;</span><br><span class="line">&#123; ContactName = Maria Anders, OrderDate = 1/15/1998 12:00:00 AM &#125;</span><br><span class="line">&#123; ContactName = Maria Anders, OrderDate = 3/16/1998 12:00:00 AM &#125;</span><br><span class="line">&#123; ContactName = Maria Anders, OrderDate = 4/9/1998 12:00:00 AM &#125;</span><br></pre></td></tr></table></figure><p>接下来就是难啃的骨头了:-)</p><h2 id="implementing-selectmany"><a class="markdownIt-Anchor" href="#implementing-selectmany"></a> Implementing SelectMany</h2><p>如果你写过SQL的话，你可能会感到很不解，为什么我说嵌套“from”子句实现起来会比较困难。毕竟在SQL里面它与显式连接仅仅是CROSS JOIN和INNER JOIN的区别而已。在LINQ这种倾向于非SQL的语言来说，CROSS JOIN其实并不是连接，而是交叉乘积。为了将它变成连接，需要在where子句中放一个连接条件来进行真正的连接操作。所以，在SQL的层面上，唯一的区别就是，CROSS JOIN将连接条件放在WHERE子句中，而INNER JOIN将连接条件放在ON子句中。好像也没什么问题。</p><p>不，还有很多问题。问题不在SQL上，大部分都在SQL以外的地方。如你所见，LINQ中的嵌套from与CROSS JOIN并不一样。有时候是一样的，但不全是。</p><p>在这个时候问题才会出现。一个连接使用连接条件将两个完全独立的子查询连接起来，这时只有连接条件才能够同时访问到两个子查询中的列。但是LINQ中的嵌套from就很不一样了，在LINQ中，内层的源表达式是可以访问到外层的源的。将它们想象为一个嵌套的foreach循环，内层循环可以访问到外层循环中的变量。</p><p>问题就在于要如何合适地翻译这种内层from子句中引用了外层的变量的查询。</p><p>如果你的查询是这样写的，那么没有问题：</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> query = <span class="keyword">from</span> c <span class="keyword">in</span> db.Customers</span><br><span class="line">            <span class="keyword">from</span> o <span class="keyword">in</span> db.Orders</span><br><span class="line">            <span class="keyword">where</span> c.CustomerID == o.CustomerID</span><br><span class="line">            <span class="keyword">select</span> <span class="keyword">new</span> &#123; c.ContactName, o.OrderDate &#125;;</span><br></pre></td></tr></table></figure><p>将其转换为等价的方法调用的形式如下：</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> query = db.Customers</span><br><span class="line">              .SelectMany(c =&gt; db.Orders, (c, o) =&gt; <span class="keyword">new</span> &#123; c, o &#125;)</span><br><span class="line">              .Where(x =&gt; x.c.CustomerID == x.o.CustomerID)</span><br><span class="line">              .Select(x =&gt; <span class="keyword">new</span> &#123; x.c.ContactName, x.o.OrderDate &#125;);</span><br></pre></td></tr></table></figure><p>这个<code>SelectMany</code>方法中的集合表达式<code>db.Orders</code>没有任何对“c”的引用。这样翻译成SQL是很容易的，因为我们可以简单地把<code>db.Customers</code>和<code>db.Orders</code>放在连接的两端。</p><p>然而，稍微换个写法的话，就像这样：</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> query = <span class="keyword">from</span> c <span class="keyword">in</span> db.Customers</span><br><span class="line">            <span class="keyword">from</span> o <span class="keyword">in</span> db.Orders.Where(o =&gt; o.CustomerID == c.CustomerID)</span><br><span class="line">            <span class="keyword">select</span> <span class="keyword">new</span> &#123; c.ContactName, o.OrderDate &#125;;</span><br></pre></td></tr></table></figure><p>现在可遇到大麻烦了。将上面的查询转换为等价的方法调用的形式如下：</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> query = db.Customers</span><br><span class="line">              .SelectMany(</span><br><span class="line">                  c =&gt; db.Orders.Where(o =&gt; c.CustomerID == o.CustomerID),</span><br><span class="line">                  (c, o) =&gt; <span class="keyword">new</span> &#123; c.ContactName, o.OrderDate &#125;</span><br><span class="line">              );</span><br></pre></td></tr></table></figure><p>现在，连接条件是作为<code>SelectMany</code>的集合表达式的一部分存在的，因此它引用了“c”。现在，翻译就再也不能简单地把两个源表达式放在SQL的连接两边了，无论是交叉连接还是内连接。</p><p>那么我要如何解决这个问题呢？我没有解决，真的，我用的是一种简单粗暴的方式。我打算利用一下微软的SQL。Microsoft SQL2005提供了一个新的连接操作符，<code>CROSS APPLY</code>，它正好与现在的这个情况具有相同的语义，这实在是一个让人高兴的巧合。<code>CROSS APPLY</code>右侧的表达式可以引用左侧表达式中的列。这就是我为什么要在定义<code>JoinType</code>枚举的时候加入<code>CrossApply</code>的原因。</p><p>大部分的LINQ to SQL引擎都会尽可能地将<code>CROSS APPLY</code>转换成<code>CROSS JOIN</code>。如果不这样做的话，LINQ to SQL在SQL2000里面可能就不能正常执行。当然，即使这样，还是有一些查询是无法转换成<code>CROSS JOIN</code>的。为了在这个示例提供程序里面添加这个特性，我还要做许多工作。虽然并不是很情愿，但是我也没有那么绝情，所以还是做了一点，算是抛砖引玉吧。我会处理一些简单的情况，将其转换成<code>CROSS JOIN</code>。</p><p>所以让我们看看代码吧。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">QueryBinder</span> : <span class="title">ExpressionVisitor</span> &#123;</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitMethodCall</span>(<span class="params">MethodCallExpression m</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (m.Method.DeclaringType == <span class="keyword">typeof</span>(Queryable) ||</span><br><span class="line">            m.Method.DeclaringType == <span class="keyword">typeof</span>(Enumerable)) &#123;</span><br><span class="line">            <span class="keyword">switch</span> (m.Method.Name) &#123;</span><br><span class="line">                ...</span><br><span class="line">                <span class="keyword">case</span> <span class="string">&quot;SelectMany&quot;</span>:</span><br><span class="line">                    <span class="keyword">if</span> (m.Arguments.Count == <span class="number">2</span>) &#123;</span><br><span class="line">                        <span class="keyword">return</span> <span class="keyword">this</span>.BindSelectMany(</span><br><span class="line">                            m.Type, m.Arguments[<span class="number">0</span>], </span><br><span class="line">                            (LambdaExpression)StripQuotes(m.Arguments[<span class="number">1</span>]),</span><br><span class="line">                            <span class="literal">null</span></span><br><span class="line">                        );</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">else</span> <span class="keyword">if</span> (m.Arguments.Count == <span class="number">3</span>) &#123;</span><br><span class="line">                        <span class="keyword">return</span> <span class="keyword">this</span>.BindSelectMany(</span><br><span class="line">                            m.Type, m.Arguments[<span class="number">0</span>], </span><br><span class="line">                            (LambdaExpression)StripQuotes(m.Arguments[<span class="number">1</span>]), </span><br><span class="line">                            (LambdaExpression)StripQuotes(m.Arguments[<span class="number">2</span>])</span><br><span class="line">                        );</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                ...</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">BindSelectMany</span>(<span class="params">Type resultType, Expression source, LambdaExpression collectionSelector, LambdaExpression resultSelector</span>)</span> &#123;</span><br><span class="line">        ProjectionExpression projection = (ProjectionExpression)<span class="keyword">this</span>.Visit(source);</span><br><span class="line">        <span class="keyword">this</span>.map[collectionSelector.Parameters[<span class="number">0</span>]] = projection.Projector;</span><br><span class="line">        ProjectionExpression collectionProjection = (ProjectionExpression)<span class="keyword">this</span>.Visit(collectionSelector.Body);</span><br><span class="line">        JoinType joinType = IsTable(collectionSelector.Body) ? JoinType.CrossJoin : JoinType.CrossApply;</span><br><span class="line">        JoinExpression <span class="keyword">join</span> = <span class="keyword">new</span> JoinExpression(resultType, joinType, projection.Source, collectionProjection.Source, <span class="literal">null</span>);</span><br><span class="line">        <span class="built_in">string</span> <span class="keyword">alias</span> = <span class="keyword">this</span>.GetNextAlias();</span><br><span class="line">        ProjectedColumns pc;</span><br><span class="line">        <span class="keyword">if</span> (resultSelector == <span class="literal">null</span>) &#123;</span><br><span class="line">            pc = <span class="keyword">this</span>.ProjectColumns(collectionProjection.Projector, <span class="keyword">alias</span>, projection.Source.Alias, collectionProjection.Source.Alias);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">this</span>.map[resultSelector.Parameters[<span class="number">0</span>]] = projection.Projector;</span><br><span class="line">            <span class="keyword">this</span>.map[resultSelector.Parameters[<span class="number">1</span>]] = collectionProjection.Projector;</span><br><span class="line">            Expression result = <span class="keyword">this</span>.Visit(resultSelector.Body);</span><br><span class="line">            pc = <span class="keyword">this</span>.ProjectColumns(result, <span class="keyword">alias</span>, projection.Source.Alias, collectionProjection.Source.Alias);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> ProjectionExpression(</span><br><span class="line">            <span class="keyword">new</span> SelectExpression(resultType, <span class="keyword">alias</span>, pc.Columns, <span class="keyword">join</span>, <span class="literal">null</span>),</span><br><span class="line">            pc.Projector</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="built_in">bool</span> <span class="title">IsTable</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        ConstantExpression c = expression <span class="keyword">as</span> ConstantExpression;</span><br><span class="line">        <span class="keyword">return</span> c != <span class="literal">null</span> &amp;&amp; IsTable(c.Value);</span><br><span class="line">    &#125;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>第一件值得注意的事情是，<code>SelectMany</code>方法有两种不同的形式。第一种形式以一个<code>source</code>表达式和一个<code>collectionSelector</code>表达式为参数。<code>collectionSelector</code>产生一系列具有相同成员类型的序列，<code>SelectMany</code>方法仅仅是将这些序列合并成一个大的序列。第二种形式多了一个<code>resultSelector</code>，它允许你从连接的两个序列中投影出自己的结果。我实现的<code>BindSelectMany</code>方法可以指定<code>resultSelector</code>参数，也可以不指定。</p><p>注意，在这个函数的第四行，我判断了应该使用哪种连接类型来表示这个<code>SelectMany</code>调用。如果我能确定<code>collectionSelector</code>只是一个简单的表查询的话，我就能够得知它没有引用任何外层查询变量（<code>collectionSelector</code> lambda表达式的参数）。这样我就可以安全地选择<code>CROSS JOIN</code>而不是<code>CROSS APPLY</code>。如果想做得更复杂一点的话可以写一个访问器来判断<code>collectionSelector</code>中到底有没有引用。也许下次我会写的，我有种预感，可能到时候我会因为其他原因而不得不这么做。但是这里只是一个简单的示例。</p><p>总而言之，这里的代码和<code>BindJoin</code>方法或其他方法里面的很不一样。我必须要处理<code>resultSelector</code>没有指定的情况。在这种情况下，我简单地重用<code>collectionProjection</code>来充当最终的投影。</p><p>让我们测试一下新的代码吧。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> query = <span class="keyword">from</span> c <span class="keyword">in</span> db.Customers</span><br><span class="line">            <span class="keyword">where</span> c.CustomerID == <span class="string">&quot;ALFKI&quot;</span></span><br><span class="line">            <span class="keyword">from</span> o <span class="keyword">in</span> db.Orders</span><br><span class="line">            <span class="keyword">where</span> c.CustomerID == o.CustomerID</span><br><span class="line">            <span class="keyword">select</span> <span class="keyword">new</span> &#123; c.ContactName, o.OrderDate &#125;;</span><br><span class="line"></span><br><span class="line">Console.WriteLine(query);</span><br><span class="line"></span><br><span class="line"><span class="keyword">foreach</span> (<span class="keyword">var</span> item <span class="keyword">in</span> query) &#123;</span><br><span class="line">    Console.WriteLine(item);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>执行上面的代码，产生如下结果：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">SELECT t6.ContactName, t6.OrderDate</span><br><span class="line">FROM (</span><br><span class="line">  SELECT t5.CustomerID, t5.ContactName, t5.Phone, t5.City, t5.Country, t5.OrderID, t5.CustomerID1, t5.OrderDate</span><br><span class="line">  FROM (</span><br><span class="line">    SELECT t2.CustomerID, t2.ContactName, t2.Phone, t2.City, t2.Country, t4.OrderID, t4.CustomerID AS CustomerID1, t4.OrderDate</span><br><span class="line">    FROM (</span><br><span class="line">      SELECT t1.CustomerID, t1.ContactName, t1.Phone, t1.City, t1.Country</span><br><span class="line">      FROM (</span><br><span class="line">        SELECT t0.CustomerID, t0.ContactName, t0.Phone, t0.City, t0.Country</span><br><span class="line">        FROM Customers AS t0</span><br><span class="line">      ) AS t1</span><br><span class="line">      WHERE (t1.CustomerID = &#x27;ALFKI&#x27;)</span><br><span class="line">    ) AS t2</span><br><span class="line">    CROSS JOIN (</span><br><span class="line">      SELECT t3.OrderID, t3.CustomerID, t3.OrderDate</span><br><span class="line">      FROM Orders AS t3</span><br><span class="line">    ) AS t4</span><br><span class="line">  ) AS t5</span><br><span class="line">  WHERE (t5.CustomerID = t5.CustomerID1)</span><br><span class="line">) AS t6</span><br><span class="line"></span><br><span class="line">&#123; ContactName = Maria Anders, OrderDate = 8/25/1997 12:00:00 AM &#125;</span><br><span class="line">&#123; ContactName = Maria Anders, OrderDate = 10/3/1997 12:00:00 AM &#125;</span><br><span class="line">&#123; ContactName = Maria Anders, OrderDate = 10/13/1997 12:00:00 AM &#125;</span><br><span class="line">&#123; ContactName = Maria Anders, OrderDate = 1/15/1998 12:00:00 AM &#125;</span><br><span class="line">&#123; ContactName = Maria Anders, OrderDate = 3/16/1998 12:00:00 AM &#125;</span><br><span class="line">&#123; ContactName = Maria Anders, OrderDate = 4/9/1998 12:00:00 AM &#125;</span><br></pre></td></tr></table></figure><p>哎呀，这个查询执行起来好像太慢了。我猜这是因为我盲目地添加新的嵌套查询而导致的。也许以后我会找个方法来去掉里面不必要的子查询:-)</p><p>当然，如果将这个查询的写法改成这种无法通过简单检查的形式的话，得到的结果就是<code>CROSS APPLY</code>。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> query = db.Customers</span><br><span class="line">              .Where(c =&gt; c.CustomerID == <span class="string">&quot;ALFKI&quot;</span>)</span><br><span class="line">              .SelectMany(</span><br><span class="line">                  c =&gt; db.Orders.Where(o =&gt; c.CustomerID == o.CustomerID),</span><br><span class="line">                  (c, o) =&gt; <span class="keyword">new</span> &#123; c.ContactName, o.OrderDate &#125;</span><br><span class="line">              );</span><br><span class="line"></span><br><span class="line">Console.WriteLine(query);</span><br><span class="line"></span><br><span class="line"><span class="keyword">foreach</span> (<span class="keyword">var</span> item <span class="keyword">in</span> query) &#123;</span><br><span class="line">    Console.WriteLine(item);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面的代码产生如下结果：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">SELECT t2.ContactName, t5.OrderDate</span><br><span class="line">FROM (</span><br><span class="line">  SELECT t1.CustomerID, t1.ContactName, t1.Phone, t1.City, t1.Country</span><br><span class="line">  FROM (</span><br><span class="line">    SELECT t0.CustomerID, t0.ContactName, t0.Phone, t0.City, t0.Country</span><br><span class="line">    FROM Customers AS t0</span><br><span class="line">  ) AS t1</span><br><span class="line">  WHERE (t1.CustomerID = &#x27;ALFKI&#x27;)</span><br><span class="line">) AS t2</span><br><span class="line">CROSS APPLY (</span><br><span class="line">  SELECT t4.OrderID, t4.CustomerID, t4.OrderDate</span><br><span class="line">  FROM (</span><br><span class="line">    SELECT t3.OrderID, t3.CustomerID, t3.OrderDate</span><br><span class="line">    FROM Orders AS t3</span><br><span class="line">  ) AS t4</span><br><span class="line">  WHERE (t2.CustomerID = t4.CustomerID)</span><br><span class="line">) AS t5</span><br><span class="line"></span><br><span class="line">&#123; ContactName = Maria Anders, OrderDate = 8/25/1997 12:00:00 AM &#125;</span><br><span class="line">&#123; ContactName = Maria Anders, OrderDate = 10/3/1997 12:00:00 AM &#125;</span><br><span class="line">&#123; ContactName = Maria Anders, OrderDate = 10/13/1997 12:00:00 AM &#125;</span><br><span class="line">&#123; ContactName = Maria Anders, OrderDate = 1/15/1998 12:00:00 AM &#125;</span><br><span class="line">&#123; ContactName = Maria Anders, OrderDate = 3/16/1998 12:00:00 AM &#125;</span><br><span class="line">&#123; ContactName = Maria Anders, OrderDate = 4/9/1998 12:00:00 AM &#125;</span><br></pre></td></tr></table></figure><p>正如我所料！</p><p>现在我的提供程序已经支持<code>Join</code>和<code>SelectMany</code>调用了，我仿佛听到了你们的欢呼声。这个提供程序的功能已经很多了，但是还是有一些明显的坑没有填，还是有一些操作符没有实现，应该给我发工资才对得起我的辛勤付出啊。</p><p><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Components.PostAttachments/00/04/75/11/61/Query7.zip">Query7.zip</a></p>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;英文原文是&lt;a href=&quot;https://social.msdn.microsoft.com/profile/matt%20warren%20-%20msft/&quot; title=&quot;Matt Warren&quot;&gt;Matt Warren&lt;/a&gt;发表在MSDN Blogs的系列文章之一，英文渣渣，翻译&lt;strong&gt;不供参考&lt;/strong&gt;，请直接&lt;a href=&quot;http://blogs.msdn.com/b/mattwar/archive/2007/09/04/linq-building-an-iqueryable-provider-part-vii.aspx&quot;&gt;看原文&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;从上篇文章到现在，已经有好几个星期没有更新了。希望在这段时间里面你们也有用自己的时间来探索如何构建自己的提供程序。我也一直在关注别人的各种各样的“LINQ to XXX”的项目，感觉都很不错。今天我将向你们介绍如何在我的提供程序中添加连接查询的功能，比起只支持select和where来，支持join将能提供更多有趣的用法。&lt;/p&gt;
&lt;h2 id=&quot;implementing-join&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#implementing-join&quot;&gt;&lt;/a&gt; Implementing Join&lt;/h2&gt;
&lt;p&gt;在LINQ中有许多种不同的连接查询的写法。在C♯或者VB中，如果写了多个from子句，将会产生笛卡尔积的结果，但如果把一个子句的键和另一个子句的键匹配起来，所得到的就是一个连接查询。&lt;/p&gt;
&lt;figure class=&quot;highlight cs&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;var&lt;/span&gt; query = &lt;span class=&quot;keyword&quot;&gt;from&lt;/span&gt; c &lt;span class=&quot;keyword&quot;&gt;in&lt;/span&gt; db.Customers&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;from&lt;/span&gt; o &lt;span class=&quot;keyword&quot;&gt;in&lt;/span&gt; db.Orders&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;where&lt;/span&gt; c.CustomerID == o.CustomerID&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &amp;#123; c.ContactName, o.OrderDate &amp;#125;;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    
    <category term="C♯" scheme="https://www.liuwj.me/tags/C-Sharp/"/>
    
  </entry>
  
  <entry>
    <title>「译」LINQ: Building an IQueryable Provider - Part VI: Nested queries</title>
    <link href="https://www.liuwj.me/posts/linq-building-an-iqueryable-provider-part-vi/"/>
    <id>https://www.liuwj.me/posts/linq-building-an-iqueryable-provider-part-vi/</id>
    <published>2016-02-13T00:00:00.000Z</published>
    <updated>2026-03-01T02:56:55.950Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>英文原文是<a href="https://social.msdn.microsoft.com/profile/matt%20warren%20-%20msft/" title="Matt Warren">Matt Warren</a>发表在MSDN Blogs的系列文章之一，英文渣渣，翻译<strong>不供参考</strong>，请直接<a href="http://blogs.msdn.com/b/mattwar/archive/2007/08/09/linq-building-an-iqueryable-provider-part-vi.aspx">看原文</a>。</p></blockquote><p>你又以为这个系列已经完成，所以我已经转移到其他阵地上去了吗？因为Select操作工作得非常好，所以你以为前面所讲的就是你构建自己的<code>IQueryable</code>提供程序所需要了解的所有内容了吗？哈！还有很多需要学习的呢，而且，Select操作还是有些漏洞。</p><h2 id="finishing-select"><a class="markdownIt-Anchor" href="#finishing-select"></a> Finishing Select</h2><p>有漏洞？怎么可能？我把你当成从来不会出错的微软大神，但是你却说你给我的是劣质的代码？我把已经把代码复制粘贴到产品里，老板已经说了下周一就启动！你怎么能这么做？（喘气）</p><p>放心啦，不是什么严重的漏洞，只是一点小小的缺陷而已。</p><p>回想一下，在上篇文章中，我建了四种表达式节点，Table，Column，Select和Projection，它们工作十分良好，不是吗？有漏洞的地方是我没有考虑到所有可以写查询表达式的地方。我考虑到的只是最明显的Projection节点出现在查询表达式树顶的情况。毕竟，因为我只支持<code>Select</code>和<code>Where</code>，所以最后一个操作必定是这两者之一。我的代码就是这样假设的。</p><p>这不是问题所在。</p><p>问题是Projection节点也有可能出现在选择器表达式里面，例如，看下面的查询。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> query = <span class="keyword">from</span> c <span class="keyword">in</span> db.Customers</span><br><span class="line">            <span class="keyword">select</span> <span class="keyword">new</span> &#123;</span><br><span class="line">                Name = c.ContactName,</span><br><span class="line">                Orders = <span class="keyword">from</span> o <span class="keyword">in</span> db.Orders</span><br><span class="line">                         <span class="keyword">where</span> o.CustomerID == c.CustomerID</span><br><span class="line">                         <span class="keyword">select</span> o</span><br><span class="line">            &#125;;</span><br></pre></td></tr></table></figure><span id="more"></span><p>我在选择器表达式里面写了一个嵌套查询，这与我们之前写的表格式的查询非常不一样。现在我希望我们的提供程序创建嵌套的对象，每个对象都有一个名字和一个订单的集合。这样的查询要怎么实现？SQL甚至都做不到这一点。即使我彻底不支持这种写法，万一有人真的这么写又会发生什么呢？</p><p>额，抛出了一个异常，然而并不是我预想的那个异常，看来代码中的bug比我预想的要多。因为这个可爱的查询在选择器表达式中有一个<code>ProjectionExpression</code>，所以我期望在编译投影器函数的时候会抛出一个异常。我之前说过添加自己的表达式节点是没问题的对吧？理由是只有我们才能看到这些节点，哈，看来是我错了。（实际上抛出来的异常是因为我在构建Projection节点的时候弄错了它们的类型而导致的，这个以后再修复。）</p><p>现在假设我已经修复了这个类型异常，我要如何处理这个嵌套的Projection节点呢？我可以捕捉这个异常，然后抛出一个自己的异常，加个道歉声明说不支持嵌套查询。但是这样的话我就不是一个好的LINQ开发者，也享受不到解决这个问题的乐趣了。</p><p>所以，让我们继续前进吧。</p><h2 id="nested-queries"><a class="markdownIt-Anchor" href="#nested-queries"></a> Nested Queries</h2><p>我希望能够将嵌套的<code>ProjectionExpression</code>转换为嵌套的查询。SQL实际上也做不到这一点，所以我必须在自己的代码做一些事情以达到这种效果。然而，在这里我并不打算做成一个超级完善的解决方案，我只要能取回数据就够了。</p><p>因为投影器函数必须要转换为可执行的代码，所以我得将里面的<code>ProjectionExpression</code>节点给替换成从某个地方获取数据以构建Orders集合的代码。数据不可能来自现有的<code>DataReader</code>，因为它只能保存表格式的结果，因此应该来自另一个<code>DataReader</code>。我真正要做的就是将<code>ProjectionExpression</code>转换成执行的时候返回这个集合的一个函数。</p><p>我们好像在之前见过类似的东西？</p><p>思考中。。。</p><p>对，这或多或少就是我们的提供程序所做的事情。呼，事情好像有点难。提供程序早已通过<code>Execute</code>方法将表达式树转换成了结果序列。我想我已经完成一半了。</p><p>所以我需要在之前的<code>ProjectionRow</code>类中添加一个执行嵌套查询的函数，它回调提供程序以执行真正的工作。</p><p>下面是<code>ProjectionRow</code>和<code>ProjectionBuilder</code>的代码。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title">ProjectionRow</span> &#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="built_in">object</span> <span class="title">GetValue</span>(<span class="params"><span class="built_in">int</span> index</span>)</span>;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="title">IEnumerable</span>&lt;<span class="title">E</span>&gt; <span class="title">ExecuteSubQuery</span>&lt;<span class="title">E</span>&gt;(<span class="params">LambdaExpression query</span>)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">ProjectionBuilder</span> : <span class="title">DbExpressionVisitor</span> &#123;</span><br><span class="line">    ParameterExpression row;</span><br><span class="line">    <span class="built_in">string</span> rowAlias;</span><br><span class="line">    <span class="keyword">static</span> MethodInfo miGetValue;</span><br><span class="line">    <span class="keyword">static</span> MethodInfo miExecuteSubQuery;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="title">ProjectionBuilder</span>()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (miGetValue == <span class="literal">null</span>) &#123;</span><br><span class="line">            miGetValue = <span class="keyword">typeof</span>(ProjectionRow).GetMethod(<span class="string">&quot;GetValue&quot;</span>);</span><br><span class="line">            miExecuteSubQuery = <span class="keyword">typeof</span>(ProjectionRow).GetMethod(<span class="string">&quot;ExecuteSubQuery&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">internal</span> LambdaExpression <span class="title">Build</span>(<span class="params">Expression expression, <span class="built_in">string</span> <span class="keyword">alias</span></span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.row = Expression.Parameter(<span class="keyword">typeof</span>(ProjectionRow), <span class="string">&quot;row&quot;</span>);</span><br><span class="line">        <span class="keyword">this</span>.rowAlias = <span class="keyword">alias</span>;</span><br><span class="line">        Expression body = <span class="keyword">this</span>.Visit(expression);</span><br><span class="line">        <span class="keyword">return</span> Expression.Lambda(body, <span class="keyword">this</span>.row);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitColumn</span>(<span class="params">ColumnExpression column</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (column.Alias == <span class="keyword">this</span>.rowAlias) &#123;</span><br><span class="line">            <span class="keyword">return</span> Expression.Convert(Expression.Call(<span class="keyword">this</span>.row, miGetValue, Expression.Constant(column.Ordinal)), column.Type);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> column;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitProjection</span>(<span class="params">ProjectionExpression proj</span>)</span> &#123;</span><br><span class="line">        LambdaExpression subQuery = Expression.Lambda(<span class="keyword">base</span>.VisitProjection(proj), <span class="keyword">this</span>.row);</span><br><span class="line">        Type elementType = TypeSystem.GetElementType(subQuery.Body.Type);</span><br><span class="line">        MethodInfo mi = miExecuteSubQuery.MakeGenericMethod(elementType);</span><br><span class="line">        <span class="keyword">return</span> Expression.Convert(</span><br><span class="line">            Expression.Call(<span class="keyword">this</span>.row, mi, Expression.Constant(subQuery)),</span><br><span class="line">            proj.Type</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>就像在遇到<code>ColumnExpression</code>时插入<code>GetValue</code>方法调用一样，在遇到<code>ProjectionExpression</code>时也要插入<code>ExecuteSubQuery</code>方法调用。</p><p>在<code>base.VisitProjection</code>调用返回之后，投影器表达式中的相应的<code>ColumnExpression</code>已经被替换掉了。我决定将投影器表达式和指向<code>ProjectionRow</code>的参数绑定在一起，刚好有一个类可以做这件事，<code>LambdaExpression</code>，因此我将它作为<code>ExecuteSubQuery</code>方法的参数类型。</p><p>注意我是将subQuery作为一个<code>ConstantExpression</code>传进去的，这是为了骗过<code>LambdaExpression.Compile</code>方法，使之注意不到我们自己增加的节点。总之我不想让我们自己增加的节点被编译。</p><p>下一个要看的是修改过的<code>ProjectionReader</code>类，当然，<code>Enumerator</code>现在也实现了<code>ExecuteSubQuery</code>方法。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">ProjectionReader</span>&lt;<span class="title">T</span>&gt; : <span class="title">IEnumerable</span>&lt;<span class="title">T</span>&gt;, <span class="title">IEnumerable</span> &#123;</span><br><span class="line">    Enumerator enumerator;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="title">ProjectionReader</span>(<span class="params">DbDataReader reader, Func&lt;ProjectionRow, T&gt; projector, IQueryProvider provider</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.enumerator = <span class="keyword">new</span> Enumerator(reader, projector, provider);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> IEnumerator&lt;T&gt; <span class="title">GetEnumerator</span>()</span> &#123;</span><br><span class="line">        Enumerator e = <span class="keyword">this</span>.enumerator;</span><br><span class="line">        <span class="keyword">if</span> (e == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> InvalidOperationException(<span class="string">&quot;Cannot enumerate more than once&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">this</span>.enumerator = <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">return</span> e;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    IEnumerator IEnumerable.GetEnumerator() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.GetEnumerator();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">class</span> <span class="title">Enumerator</span> : <span class="title">ProjectionRow</span>, <span class="title">IEnumerator</span>&lt;<span class="title">T</span>&gt;, <span class="title">IEnumerator</span>, <span class="title">IDisposable</span> &#123;</span><br><span class="line">        DbDataReader reader;</span><br><span class="line">        T current;</span><br><span class="line">        Func&lt;ProjectionRow, T&gt; projector;</span><br><span class="line">        IQueryProvider provider;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">internal</span> <span class="title">Enumerator</span>(<span class="params">DbDataReader reader, Func&lt;ProjectionRow, T&gt; projector, IQueryProvider provider</span>)</span> &#123;</span><br><span class="line">            <span class="keyword">this</span>.reader = reader;</span><br><span class="line">            <span class="keyword">this</span>.projector = projector;</span><br><span class="line">            <span class="keyword">this</span>.provider = provider;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">object</span> <span class="title">GetValue</span>(<span class="params"><span class="built_in">int</span> index</span>)</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (index &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">if</span> (<span class="keyword">this</span>.reader.IsDBNull(index)) &#123;</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="keyword">return</span> <span class="keyword">this</span>.reader.GetValue(index);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> IndexOutOfRangeException();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="title">IEnumerable</span>&lt;<span class="title">E</span>&gt; <span class="title">ExecuteSubQuery</span>&lt;<span class="title">E</span>&gt;(<span class="params">LambdaExpression query</span>)</span> &#123;</span><br><span class="line">            ProjectionExpression projection = (ProjectionExpression) <span class="keyword">new</span> Replacer().Replace(query.Body, query.Parameters[<span class="number">0</span>], Expression.Constant(<span class="keyword">this</span>));</span><br><span class="line">            projection = (ProjectionExpression) Evaluator.PartialEval(projection, CanEvaluateLocally);</span><br><span class="line">            IEnumerable&lt;E&gt; result = (IEnumerable&lt;E&gt;)<span class="keyword">this</span>.provider.Execute(projection);</span><br><span class="line">            List&lt;E&gt; list = <span class="keyword">new</span> List&lt;E&gt;(result);</span><br><span class="line">            <span class="keyword">if</span> (<span class="keyword">typeof</span>(IQueryable&lt;E&gt;).IsAssignableFrom(query.Body.Type)) &#123;</span><br><span class="line">                <span class="keyword">return</span> list.AsQueryable();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> list;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="built_in">bool</span> <span class="title">CanEvaluateLocally</span>(<span class="params">Expression expression  &#123;</span></span></span><br><span class="line"><span class="params"><span class="function">            <span class="keyword">if</span> (expression.NodeType == ExpressionType.Parameter ||</span></span></span><br><span class="line"><span class="params"><span class="function">                expression.NodeType.IsDbExpression(</span>))</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> T Current &#123;</span><br><span class="line">            <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.current; &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="built_in">object</span> IEnumerator.Current &#123;</span><br><span class="line">            <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.current; &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="built_in">bool</span> <span class="title">MoveNext</span>()</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (<span class="keyword">this</span>.reader.Read()) &#123;</span><br><span class="line">                <span class="keyword">this</span>.current = <span class="keyword">this</span>.projector(<span class="keyword">this</span>);</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Reset</span>()</span> &#123;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Dispose</span>()</span> &#123;</span><br><span class="line">            <span class="keyword">this</span>.reader.Dispose();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我在创建<code>ProjectionReader</code>时将provider的实例传了进去，它在下面的<code>ExecuteSubQuery</code>中执行子查询时会用到。</p><p>看<code>ExecuteSubQuery</code>方法，hey，那个<code>Replacer.Replace</code>是个什么鬼？</p><p>我还没有告诉你这个类是什么，待会会给出它的代码，我们先来解释一下<code>ExecuteSubQuery</code>方法干了什么。我们获得了一个<code>LambdaExpression</code>类型的参数，它的body是内查询原始的<code>ProjectionExpression</code>，parameter是指向当前<code>ProjectionRow</code>的引用。虽然一切都是极好的，但问题是我不能通过回调provider来执行这个表达式，因为所有引用了外层查询（想想Where子句里面的连接条件）的<code>ColumnExpression</code>现在都被替换成了<code>GetValue</code>表达式。</p><p>没错，我在内层查询里面引用了外层查询，我不能让这些<code>GetValue</code>继续留在表达式中，因为这样的话子查询在执行的时候会尝试去访问不存在的列，好囧。</p><p>思考中。。。</p><p>啊哈，想到了！这些<code>GetValue</code>方法要获取的数据其实早就可用，并且近在咫尺，这些数据就在<code>DataReader</code>当前行里面。所以我想做的就是以某种方式将这些表达式的值马上“计算”出来，强制子表达式调用<code>GetValue</code>方法。要是已经有代码来做这件事那就太完美了。</p><p>等等，这不正是<code>Evaluator.PartialEval</code>方法的工作吗？当然，但是在这里并不管用。为什么？因为这些表达式引用了<code>ProjectionRow</code>参数，而<code>ParameterExpression</code>又是让<code>Evaluator</code>类不对其进行计算的标志。如果我能去掉这些参数引用，将其替换为指向当前<code>ProjectionRow</code>实例的常量表达式的话，就可以使用<code>Evaluator.PartialEval</code>方法将它们替换为实际的值了。这样一切都好办了。</p><p>怎么做呢？我需要一个工具，它查找表达式树中的节点，并将其替换为另一个节点。</p><p>下面是<code>Replacer</code>类，它简单地遍历一棵树，寻找一个节点的引用，将其替换为另一个不同节点的引用。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">Replacer</span> : <span class="title">DbExpressionVisitor</span> &#123;</span><br><span class="line">    Expression searchFor;</span><br><span class="line">    Expression replaceWith;</span><br><span class="line">    <span class="function"><span class="keyword">internal</span> Expression <span class="title">Replace</span>(<span class="params">Expression expression, Expression searchFor, Expression replaceWith</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.searchFor = searchFor;</span><br><span class="line">        <span class="keyword">this</span>.replaceWith = replaceWith;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.Visit(expression);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">Visit</span>(<span class="params">Expression exp</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (exp == <span class="keyword">this</span>.searchFor) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">this</span>.replaceWith;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">base</span>.Visit(exp);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>漂亮，我都被自己的机智吓到了。</p><p>好了，现在我已经可以将那些讨厌的<code>ProjectionRow</code>参数的引用替换成实际的对象，这就是<code>ExecuteSubQuery</code>方法的第一行所做的事情。然而这仅花了几十行英文就解释清楚了:-)</p><p>如我所愿，第二行调用了<code>Execute.PartialEval</code>方法。下一行紧接着又调用了provider来执行子查询！撒花！然后我将结果放到了一个List对象中，最后我有可能还要再将它转成<code>IQueryable</code>。我知道这很奇怪，但是这个原生查询中<code>Orders</code>属性的类型就是<code>IQueryable&lt;Order&gt;</code>，这就是<code>IQueryable</code>查询操作符的工作方式，所以C♯创造了匿名类型以充当成员类型。如果我尝试直接返回list的话，将结果组合到一起的投影器就会报错。幸运的是，已经有了将<code>IEnumerable</code>转换成<code>IQueryable</code>的方法，<code>Queryable.AsQueryable</code>。</p><p>哇！这些组件就好像被精妙设计出来的一样，能够完美地协同工作了。</p><p>大揭秘：我小小作了个弊。我改了<code>Evaluator</code>类，使它能够识别我自己添加的表达式类型。我知道，我知道，我说过其他人没必要知道它们的存在，但是<code>Evaluator</code>也是我自己的代码，所以我觉得这样并没有问题。我在附件的zip文件中附带了这个小小的修改，在这里我只放出有大修改的代码，那点小修改就不放出来了。</p><p>我还得写一个新的<code>CanEvaluateLocally</code>规则以供<code>Evaluator</code>类使用，我得确保它不会将我自己添加的那些节点视为可计算的。</p><p>所以让我们来看看<code>DbQueryProvider</code>有什么变化吧。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">DbQueryProvider</span> : <span class="title">QueryProvider</span> &#123;</span><br><span class="line">    DbConnection connection;</span><br><span class="line">    TextWriter log;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">DbQueryProvider</span>(<span class="params">DbConnection connection</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.connection = connection;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> TextWriter Log &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.log; &#125;</span><br><span class="line">        <span class="keyword">set</span> &#123; <span class="keyword">this</span>.log = <span class="keyword">value</span>; &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">string</span> <span class="title">GetQueryText</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.Translate(expression).CommandText;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">object</span> <span class="title">Execute</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.Execute(<span class="keyword">this</span>.Translate(expression));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="built_in">object</span> <span class="title">Execute</span>(<span class="params">TranslateResult query</span>)</span> &#123;</span><br><span class="line">        Delegate projector = query.Projector.Compile();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">this</span>.log != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">this</span>.log.WriteLine(query.CommandText);</span><br><span class="line">            <span class="keyword">this</span>.log.WriteLine();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        DbCommand cmd = <span class="keyword">this</span>.connection.CreateCommand();</span><br><span class="line">        cmd.CommandText = query.CommandText;</span><br><span class="line">        DbDataReader reader = cmd.ExecuteReader();</span><br><span class="line"></span><br><span class="line">        Type elementType = TypeSystem.GetElementType(query.Projector.Body.Type);</span><br><span class="line">        <span class="keyword">return</span> Activator.CreateInstance(</span><br><span class="line">            <span class="keyword">typeof</span>(ProjectionReader&lt;&gt;).MakeGenericType(elementType),</span><br><span class="line">            BindingFlags.Instance | BindingFlags.NonPublic, <span class="literal">null</span>,</span><br><span class="line">            <span class="keyword">new</span> <span class="built_in">object</span>[] &#123; reader, projector, <span class="keyword">this</span> &#125;,</span><br><span class="line">            <span class="literal">null</span></span><br><span class="line">            );</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">TranslateResult</span> &#123;</span><br><span class="line">        <span class="keyword">internal</span> <span class="built_in">string</span> CommandText;</span><br><span class="line">        <span class="keyword">internal</span> LambdaExpression Projector;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> TranslateResult <span class="title">Translate</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        ProjectionExpression projection = expression <span class="keyword">as</span> ProjectionExpression;</span><br><span class="line">        <span class="keyword">if</span> (projection == <span class="literal">null</span>) &#123;</span><br><span class="line">            expression = Evaluator.PartialEval(expression);</span><br><span class="line">            projection = (ProjectionExpression)<span class="keyword">new</span> QueryBinder().Bind(expression);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="built_in">string</span> commandText = <span class="keyword">new</span> QueryFormatter().Format(projection.Source);</span><br><span class="line">        LambdaExpression projector = <span class="keyword">new</span> ProjectionBuilder().Build(projection.Projector, projection.Source.Alias);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> TranslateResult &#123; CommandText = commandText, Projector = projector &#125;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>唯一有变化的是<code>Translate</code>方法。当传进来的参数是<code>ProjectionExpression</code>时，就不再进行将表达式转换成<code>ProjectionExpression</code>的操作，而是直接跳到构建SQL命令和投影器的步骤。</p><p>差点忘记，我还添加了类似LINQ to SQL的日志的特性，它能帮助我们看清背后的执行过程。我的上下文类里面也加了<code>Log</code>属性。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Northwind</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> Query&lt;Customers&gt; Customers;</span><br><span class="line">    <span class="keyword">public</span> Query&lt;Orders&gt; Orders;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> DbQueryProvider provider;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Northwind</span>(<span class="params">DbConnection connection</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.provider = <span class="keyword">new</span> DbQueryProvider(connection);</span><br><span class="line">        <span class="keyword">this</span>.Customers = <span class="keyword">new</span> Query&lt;Customers&gt;(<span class="keyword">this</span>.provider);</span><br><span class="line">        <span class="keyword">this</span>.Orders = <span class="keyword">new</span> Query&lt;Orders&gt;(<span class="keyword">this</span>.provider);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> TextWriter Log &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.provider.Log; &#125;</span><br><span class="line">        <span class="keyword">set</span> &#123; <span class="keyword">this</span>.provider.Log = <span class="keyword">value</span>; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="taking-it-for-a-spin"><a class="markdownIt-Anchor" href="#taking-it-for-a-spin"></a> Taking it for a Spin</h2><p>现在，让我们试试这个新的魔法般的特性把。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">string</span> city = <span class="string">&quot;London&quot;</span>;</span><br><span class="line"><span class="keyword">var</span> query = <span class="keyword">from</span> c <span class="keyword">in</span> db.Customers</span><br><span class="line">            <span class="keyword">where</span> c.City == city</span><br><span class="line">            <span class="keyword">select</span> <span class="keyword">new</span> &#123;</span><br><span class="line">                Name = c.ContactName,</span><br><span class="line">                Orders = <span class="keyword">from</span> o <span class="keyword">in</span> db.Orders</span><br><span class="line">                         <span class="keyword">where</span> o.CustomerID == c.CustomerID</span><br><span class="line">                         <span class="keyword">select</span> o</span><br><span class="line">            &#125;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">   <span class="keyword">foreach</span> (<span class="keyword">var</span> item <span class="keyword">in</span> query) &#123;</span><br><span class="line">       Console.WriteLine(<span class="string">&quot;&#123;0&#125;&quot;</span>, item.Name);</span><br><span class="line">       <span class="keyword">foreach</span> (<span class="keyword">var</span> ord <span class="keyword">in</span> item.Orders) &#123;</span><br><span class="line">           Console.WriteLine(<span class="string">&quot;\tOrder: &#123;0&#125;&quot;</span>, ord.OrderID);</span><br><span class="line">       &#125;</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><p>执行上面的代码，产生如下输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line">Thomas Hardy</span><br><span class="line">        Order: 10355</span><br><span class="line">        Order: 10383</span><br><span class="line">        Order: 10453</span><br><span class="line">        Order: 10558</span><br><span class="line">        Order: 10707</span><br><span class="line">        Order: 10741</span><br><span class="line">        Order: 10743</span><br><span class="line">        Order: 10768</span><br><span class="line">        Order: 10793</span><br><span class="line">        Order: 10864</span><br><span class="line">        Order: 10920</span><br><span class="line">        Order: 10953</span><br><span class="line">        Order: 11016</span><br><span class="line">Victoria Ashworth</span><br><span class="line">        Order: 10289</span><br><span class="line">        Order: 10471</span><br><span class="line">        Order: 10484</span><br><span class="line">        Order: 10538</span><br><span class="line">        Order: 10539</span><br><span class="line">        Order: 10578</span><br><span class="line">        Order: 10599</span><br><span class="line">        Order: 10943</span><br><span class="line">        Order: 10947</span><br><span class="line">        Order: 11023</span><br><span class="line">Elizabeth Brown</span><br><span class="line">        Order: 10435</span><br><span class="line">        Order: 10462</span><br><span class="line">        Order: 10848</span><br><span class="line">Ann Devon</span><br><span class="line">        Order: 10364</span><br><span class="line">        Order: 10400</span><br><span class="line">        Order: 10532</span><br><span class="line">        Order: 10726</span><br><span class="line">        Order: 10987</span><br><span class="line">        Order: 11024</span><br><span class="line">        Order: 11047</span><br><span class="line">        Order: 11056</span><br><span class="line">Simon Crowther</span><br><span class="line">        Order: 10517</span><br><span class="line">        Order: 10752</span><br><span class="line">        Order: 11057</span><br><span class="line">Hari Kumar</span><br><span class="line">        Order: 10359</span><br><span class="line">        Order: 10377</span><br><span class="line">        Order: 10388</span><br><span class="line">        Order: 10472</span><br><span class="line">        Order: 10523</span><br><span class="line">        Order: 10547</span><br><span class="line">        Order: 10800</span><br><span class="line">        Order: 10804</span><br><span class="line">        Order: 10869</span><br></pre></td></tr></table></figure><p>下面是查询的执行过程（我用了新的<code>Log</code>属性捕捉到的）：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> t2.ContactName, t2.CustomerID</span><br><span class="line"><span class="keyword">FROM</span> (</span><br><span class="line">  <span class="keyword">SELECT</span> t1.CustomerID, t1.ContactName, t1.Phone, t1.City, t1.Country</span><br><span class="line">  <span class="keyword">FROM</span> (</span><br><span class="line">    <span class="keyword">SELECT</span> t0.CustomerID, t0.ContactName, t0.Phone, t0.City, t0.Country</span><br><span class="line">    <span class="keyword">FROM</span> Customers <span class="keyword">AS</span> t0</span><br><span class="line">  ) <span class="keyword">AS</span> t1</span><br><span class="line">  <span class="keyword">WHERE</span> (t1.City <span class="operator">=</span> <span class="string">&#x27;London&#x27;</span>)</span><br><span class="line">) <span class="keyword">AS</span> t2</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> t4.OrderID, t4.CustomerID, t4.OrderDate</span><br><span class="line"><span class="keyword">FROM</span> (</span><br><span class="line">  <span class="keyword">SELECT</span> t3.OrderID, t3.CustomerID, t3.OrderDate</span><br><span class="line">  <span class="keyword">FROM</span> Orders <span class="keyword">AS</span> t3</span><br><span class="line">) <span class="keyword">AS</span> t4</span><br><span class="line"><span class="keyword">WHERE</span> (t4.CustomerID <span class="operator">=</span> <span class="string">&#x27;AROUT&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> t4.OrderID, t4.CustomerID, t4.OrderDate</span><br><span class="line"><span class="keyword">FROM</span> (</span><br><span class="line">  <span class="keyword">SELECT</span> t3.OrderID, t3.CustomerID, t3.OrderDate</span><br><span class="line">  <span class="keyword">FROM</span> Orders <span class="keyword">AS</span> t3</span><br><span class="line">) <span class="keyword">AS</span> t4</span><br><span class="line"><span class="keyword">WHERE</span> (t4.CustomerID <span class="operator">=</span> <span class="string">&#x27;BSBEV&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> t4.OrderID, t4.CustomerID, t4.OrderDate</span><br><span class="line"><span class="keyword">FROM</span> (</span><br><span class="line">  <span class="keyword">SELECT</span> t3.OrderID, t3.CustomerID, t3.OrderDate</span><br><span class="line">  <span class="keyword">FROM</span> Orders <span class="keyword">AS</span> t3</span><br><span class="line">) <span class="keyword">AS</span> t4</span><br><span class="line"><span class="keyword">WHERE</span> (t4.CustomerID <span class="operator">=</span> <span class="string">&#x27;CONSH&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> t4.OrderID, t4.CustomerID, t4.OrderDate</span><br><span class="line"><span class="keyword">FROM</span> (</span><br><span class="line">  <span class="keyword">SELECT</span> t3.OrderID, t3.CustomerID, t3.OrderDate</span><br><span class="line">  <span class="keyword">FROM</span> Orders <span class="keyword">AS</span> t3</span><br><span class="line">) <span class="keyword">AS</span> t4</span><br><span class="line"><span class="keyword">WHERE</span> (t4.CustomerID <span class="operator">=</span> <span class="string">&#x27;EASTC&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> t4.OrderID, t4.CustomerID, t4.OrderDate</span><br><span class="line"><span class="keyword">FROM</span> (</span><br><span class="line">  <span class="keyword">SELECT</span> t3.OrderID, t3.CustomerID, t3.OrderDate</span><br><span class="line">  <span class="keyword">FROM</span> Orders <span class="keyword">AS</span> t3</span><br><span class="line">) <span class="keyword">AS</span> t4</span><br><span class="line"><span class="keyword">WHERE</span> (t4.CustomerID <span class="operator">=</span> <span class="string">&#x27;NORTS&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span> t4.OrderID, t4.CustomerID, t4.OrderDate</span><br><span class="line"><span class="keyword">FROM</span> (</span><br><span class="line">  <span class="keyword">SELECT</span> t3.OrderID, t3.CustomerID, t3.OrderDate</span><br><span class="line">  <span class="keyword">FROM</span> Orders <span class="keyword">AS</span> t3</span><br><span class="line">) <span class="keyword">AS</span> t4</span><br><span class="line"><span class="keyword">WHERE</span> (t4.CustomerID <span class="operator">=</span> <span class="string">&#x27;SEVES&#x27;</span>)</span><br></pre></td></tr></table></figure><p>虽然让内层查询执行许多次不是很理想，但是总比直接抛出一个异常要好。</p><p>现在，Select操作已经最终完成了，它现在已经可以支持任意的投影了。也许吧:-)</p><p><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Components.PostAttachments/00/04/31/53/48/Query6.zip">Query6.zip</a></p>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;英文原文是&lt;a href=&quot;https://social.msdn.microsoft.com/profile/matt%20warren%20-%20msft/&quot; title=&quot;Matt Warren&quot;&gt;Matt Warren&lt;/a&gt;发表在MSDN Blogs的系列文章之一，英文渣渣，翻译&lt;strong&gt;不供参考&lt;/strong&gt;，请直接&lt;a href=&quot;http://blogs.msdn.com/b/mattwar/archive/2007/08/09/linq-building-an-iqueryable-provider-part-vi.aspx&quot;&gt;看原文&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;你又以为这个系列已经完成，所以我已经转移到其他阵地上去了吗？因为Select操作工作得非常好，所以你以为前面所讲的就是你构建自己的&lt;code&gt;IQueryable&lt;/code&gt;提供程序所需要了解的所有内容了吗？哈！还有很多需要学习的呢，而且，Select操作还是有些漏洞。&lt;/p&gt;
&lt;h2 id=&quot;finishing-select&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#finishing-select&quot;&gt;&lt;/a&gt; Finishing Select&lt;/h2&gt;
&lt;p&gt;有漏洞？怎么可能？我把你当成从来不会出错的微软大神，但是你却说你给我的是劣质的代码？我把已经把代码复制粘贴到产品里，老板已经说了下周一就启动！你怎么能这么做？（喘气）&lt;/p&gt;
&lt;p&gt;放心啦，不是什么严重的漏洞，只是一点小小的缺陷而已。&lt;/p&gt;
&lt;p&gt;回想一下，在上篇文章中，我建了四种表达式节点，Table，Column，Select和Projection，它们工作十分良好，不是吗？有漏洞的地方是我没有考虑到所有可以写查询表达式的地方。我考虑到的只是最明显的Projection节点出现在查询表达式树顶的情况。毕竟，因为我只支持&lt;code&gt;Select&lt;/code&gt;和&lt;code&gt;Where&lt;/code&gt;，所以最后一个操作必定是这两者之一。我的代码就是这样假设的。&lt;/p&gt;
&lt;p&gt;这不是问题所在。&lt;/p&gt;
&lt;p&gt;问题是Projection节点也有可能出现在选择器表达式里面，例如，看下面的查询。&lt;/p&gt;
&lt;figure class=&quot;highlight cs&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;var&lt;/span&gt; query = &lt;span class=&quot;keyword&quot;&gt;from&lt;/span&gt; c &lt;span class=&quot;keyword&quot;&gt;in&lt;/span&gt; db.Customers&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &lt;span class=&quot;keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;new&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                Name = c.ContactName,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                Orders = &lt;span class=&quot;keyword&quot;&gt;from&lt;/span&gt; o &lt;span class=&quot;keyword&quot;&gt;in&lt;/span&gt; db.Orders&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                         &lt;span class=&quot;keyword&quot;&gt;where&lt;/span&gt; o.CustomerID == c.CustomerID&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;                         &lt;span class=&quot;keyword&quot;&gt;select&lt;/span&gt; o&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;            &amp;#125;;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;</summary>
    
    
    
    
    <category term="C♯" scheme="https://www.liuwj.me/tags/C-Sharp/"/>
    
  </entry>
  
  <entry>
    <title>「译」LINQ: Building an IQueryable Provider - Part V: Improved Column binding</title>
    <link href="https://www.liuwj.me/posts/linq-building-an-iqueryable-provider-part-v/"/>
    <id>https://www.liuwj.me/posts/linq-building-an-iqueryable-provider-part-v/</id>
    <published>2016-02-04T00:00:00.000Z</published>
    <updated>2026-03-01T02:56:55.950Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>英文原文是<a href="https://social.msdn.microsoft.com/profile/matt%20warren%20-%20msft/" title="Matt Warren">Matt Warren</a>发表在MSDN Blogs的系列文章之一，英文渣渣，翻译<strong>不供参考</strong>，请直接<a href="http://blogs.msdn.com/b/mattwar/archive/2007/08/03/linq-building-an-iqueryable-provider-part-v.aspx">看原文</a>。</p></blockquote><p>在前面四篇文章里面，我构建了一个LINQ IQueryable提供程序，它可将<code>Queryable.Where</code>和<code>Queryable.Select</code>两个标准查询操作符翻译成SQL，并通过ADO送到数据库中去执行。虽然已经做得很不错，但是这个提供程序还是有一些漏洞，而且我还没有提到其他的查询操作，比如OrderBy和Join等等。如果认为用户写出的查询都像我的demo一样这么理想化的话，你可能就会掉进大坑里去。</p><span id="more"></span><h2 id="fixing-the-gaping-holes"><a class="markdownIt-Anchor" href="#fixing-the-gaping-holes"></a> Fixing the Gaping Holes</h2><p>我确实可以写出一个简单的带有where和select的运行良好的查询，就算这个查询再复杂也没关系。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> query = db.Customers.Where(c =&gt; c.City == city)</span><br><span class="line">                        .Select(c =&gt; <span class="keyword">new</span> &#123;</span><br><span class="line">                            Name = c.ContactName,</span><br><span class="line">                            Location = c.City </span><br><span class="line">                        &#125;);</span><br></pre></td></tr></table></figure><p>然而，只要将Where和Select的顺序换一下就坑爹了。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> query = db.Customers.Select(c =&gt; <span class="keyword">new</span> &#123;</span><br><span class="line">                            Name = c.ContactName,</span><br><span class="line">                            Location = c.City </span><br><span class="line">                        &#125;)</span><br><span class="line">                        .Where(x =&gt; x.Location == city);</span><br></pre></td></tr></table></figure><p>这个风骚的小查询生成了一条错误的SQL。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> (<span class="keyword">SELECT</span> ContactName, City <span class="keyword">FROM</span> (<span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> Customers) <span class="keyword">AS</span> T) <span class="keyword">AS</span> T <span class="keyword">WHERE</span> (Location <span class="operator">=</span> <span class="string">&#x27;London&#x27;</span>)</span><br></pre></td></tr></table></figure><p>在执行的时候也会抛出异常，“Invalid column name ‘Location’”。似乎我之前直接将成员访问当成数据库列引用的太过简单的做法不太行得通。我天真地假设子树里面唯一的成员访问会与Select子句中的列的名字相匹配，然而实际上并不是。所以，现在要么改一改Select子句中的列名，使之与成员的名字一致，要么想个其它的方法来解决这个问题。</p><p>我认为两种方法都是可以的，但是，考虑一个复杂一点的情况，不仅仅是将列重命名，如果选择表达式还生成了嵌套的对象，这样的话对成员的引用很可能就是一个“多点”的嵌套操作。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> query = db.Customers.Select(c =&gt; <span class="keyword">new</span> &#123;</span><br><span class="line">                            Name = c.ContactName,</span><br><span class="line">                            Location = <span class="keyword">new</span> &#123;</span><br><span class="line">                                City = c.City,</span><br><span class="line">                                Country = c.Country</span><br><span class="line">                            &#125; </span><br><span class="line">                        &#125;)</span><br><span class="line">                        .Where(x =&gt; x.Location.City == city);</span><br></pre></td></tr></table></figure><p>现在我要怎么翻译这个查询呢？已有的代码甚至根本就不能理解这个中间对象<code>Location</code>是个什么东西。幸运的是我早就知道应该怎么做了，只不过要对代码做出比较大的改动。我们需要重新审视一下提供程序仅仅只是将查询表达式翻译为文本的思路了。我们应该将查询表达式翻译为SQL，而文本只是SQL的一种表现形式，而且它还不方便我们对其施加编程逻辑。当然我们最终需要的还是文本，但如果我们能先把SQL表示为一个抽象，那么就能进行更复杂的翻译。</p><p>当然，最方便我们操作的数据结构是SQL语义树。所以，理论上我应该定义一个完整的独立的SQL语义树，将LINQ查询表达式翻译为一颗SQL语义树而不是文本，但是这样做的工作量太大了。幸运的是这个假想的SQL树的定义与LINQ表达式树的定义有很大的交集，所以我们可以偷下懒，简单地将LINQ表达式树当成SQL树来使用。为了这么做，我要添加一些新的表达式节点类型，其他的LINQ API不识别这些类型也没关系，因为这只是给我们自己使用的。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="built_in">enum</span> DbExpressionType &#123;</span><br><span class="line">    Table = <span class="number">1000</span>, <span class="comment">// make sure these don&#x27;t overlap with ExpressionType</span></span><br><span class="line">    Column,</span><br><span class="line">    Select,</span><br><span class="line">    Projection</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">TableExpression</span> : <span class="title">Expression</span> &#123;</span><br><span class="line">    <span class="built_in">string</span> <span class="keyword">alias</span>;</span><br><span class="line">    <span class="built_in">string</span> name;</span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="title">TableExpression</span>(<span class="params">Type type, <span class="built_in">string</span> <span class="keyword">alias</span>, <span class="built_in">string</span> name</span>)</span></span><br><span class="line"><span class="function">        : <span class="title">base</span>(<span class="params">(ExpressionType</span>)DbExpressionType.Table, type)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.<span class="keyword">alias</span> = <span class="keyword">alias</span>;</span><br><span class="line">        <span class="keyword">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">internal</span> <span class="built_in">string</span> Alias &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.<span class="keyword">alias</span>; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">internal</span> <span class="built_in">string</span> Name &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.name; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">ColumnExpression</span> : <span class="title">Expression</span> &#123;</span><br><span class="line">    <span class="built_in">string</span> <span class="keyword">alias</span>;</span><br><span class="line">    <span class="built_in">string</span> name;</span><br><span class="line">    <span class="built_in">int</span> ordinal;</span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="title">ColumnExpression</span>(<span class="params">Type type, <span class="built_in">string</span> <span class="keyword">alias</span>, <span class="built_in">string</span> name, <span class="built_in">int</span> ordinal</span>)</span></span><br><span class="line"><span class="function">        : <span class="title">base</span>(<span class="params">(ExpressionType</span>)DbExpressionType.Column, type)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.<span class="keyword">alias</span> = <span class="keyword">alias</span>;</span><br><span class="line">        <span class="keyword">this</span>.name = name;</span><br><span class="line">        <span class="keyword">this</span>.ordinal = ordinal;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">internal</span> <span class="built_in">string</span> Alias &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.<span class="keyword">alias</span>; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">internal</span> <span class="built_in">string</span> Name &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.name; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">internal</span> <span class="built_in">int</span> Ordinal &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.ordinal; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">ColumnDeclaration</span> &#123;</span><br><span class="line">    <span class="built_in">string</span> name;</span><br><span class="line">    Expression expression;</span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="title">ColumnDeclaration</span>(<span class="params"><span class="built_in">string</span> name, Expression expression</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.name = name;</span><br><span class="line">        <span class="keyword">this</span>.expression = expression;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">internal</span> <span class="built_in">string</span> Name &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.name; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">internal</span> Expression Expression &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.expression; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">SelectExpression</span> : <span class="title">Expression</span> &#123;</span><br><span class="line">    <span class="built_in">string</span> <span class="keyword">alias</span>;</span><br><span class="line">    ReadOnlyCollection&lt;ColumnDeclaration&gt; columns;</span><br><span class="line">    Expression <span class="keyword">from</span>;</span><br><span class="line">    Expression <span class="keyword">where</span>;</span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="title">SelectExpression</span>(<span class="params">Type type, <span class="built_in">string</span> <span class="keyword">alias</span>, IEnumerable&lt;ColumnDeclaration&gt; columns, Expression <span class="keyword">from</span>, Expression <span class="keyword">where</span></span>)</span></span><br><span class="line"><span class="function">        : <span class="title">base</span>(<span class="params">(ExpressionType</span>)DbExpressionType.Select, type)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.<span class="keyword">alias</span> = <span class="keyword">alias</span>;</span><br><span class="line">        <span class="keyword">this</span>.columns = columns <span class="keyword">as</span> ReadOnlyCollection&lt;ColumnDeclaration&gt;;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">this</span>.columns == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">this</span>.columns = <span class="keyword">new</span> List&lt;ColumnDeclaration&gt;(columns).AsReadOnly();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">this</span>.<span class="keyword">from</span> = <span class="keyword">from</span>;</span><br><span class="line">        <span class="keyword">this</span>.<span class="keyword">where</span> = <span class="keyword">where</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">internal</span> <span class="built_in">string</span> Alias &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.<span class="keyword">alias</span>; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">internal</span> ReadOnlyCollection&lt;ColumnDeclaration&gt; Columns &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.columns; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">internal</span> Expression From &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.<span class="keyword">from</span>; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">internal</span> Expression Where &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.<span class="keyword">where</span>; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">ProjectionExpression</span> : <span class="title">Expression</span> &#123;</span><br><span class="line">    SelectExpression source;</span><br><span class="line">    Expression projector;</span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="title">ProjectionExpression</span>(<span class="params">SelectExpression source, Expression projector</span>)</span></span><br><span class="line"><span class="function">        : <span class="title">base</span>(<span class="params">(ExpressionType</span>)DbExpressionType.Projection, projector.Type)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.source = source;</span><br><span class="line">        <span class="keyword">this</span>.projector = projector;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">internal</span> SelectExpression Source &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.source; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">internal</span> Expression Projector &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.projector; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我只需要在LINQ表达式树中加上SQL Select查询的概念，Select查询产生一列或多列、一个对列的引用、一个对表的引用、和一个将列引用重新组装为对象的投影器。</p><p>我继续定义了一个自己的枚举类型<code>DbExpressionType</code>，它“扩展”了基本的枚举类型<code>ExpressionType</code>，选了一个足够大的起始值以免与其他的定义冲突。如果枚举类型可以继承的话我会直接继承<code>ExpressionType</code>的，但是机智如我，就算不能继承也没有关系。</p><p>每个新的表达式节点都遵循LINQ表达式的所有模式，比如不可变等等，只不过它们现在表示的是SQL的概念，而不是CLR的概念。注意<code>SelectExpression</code>包含了一个列的集合，一个from和一个where表达式，它们对应于一条合法的SQL所具有的各种子句。</p><p><code>ProjectionExpression</code>描述了如何从<code>SelectExpression</code>的列中构造出结果。仔细想想就知道，它和Part IV里面为<code>ProjectionReader</code>构造委托的投影器表达式几乎是一样的。只不过现在它的作用不仅仅是组装此<code>DataReader</code>中读出来的数据，它还表示了SQL查询中的投影操作。</p><p>有了新的节点类型之后，当然就要有新的访问器。<code>DbExpressionVisitor</code>继承了<code>ExpressionVisitor</code>，添加了对新的节点类型的基本的访问模式。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">DbExpressionVisitor</span> : <span class="title">ExpressionVisitor</span> &#123;</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">Visit</span>(<span class="params">Expression exp</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (exp == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">switch</span> ((DbExpressionType)exp.NodeType) &#123;</span><br><span class="line">            <span class="keyword">case</span> DbExpressionType.Table:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.VisitTable((TableExpression)exp);</span><br><span class="line">            <span class="keyword">case</span> DbExpressionType.Column:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.VisitColumn((ColumnExpression)exp);</span><br><span class="line">            <span class="keyword">case</span> DbExpressionType.Select:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.VisitSelect((SelectExpression)exp);</span><br><span class="line">            <span class="keyword">case</span> DbExpressionType.Projection:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.VisitProjection((ProjectionExpression)exp);</span><br><span class="line">            <span class="literal">default</span>:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">base</span>.Visit(exp);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">VisitTable</span>(<span class="params">TableExpression table</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> table;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">VisitColumn</span>(<span class="params">ColumnExpression column</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> column;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">VisitSelect</span>(<span class="params">SelectExpression <span class="keyword">select</span></span>)</span> &#123;</span><br><span class="line">        Expression <span class="keyword">from</span> = <span class="keyword">this</span>.VisitSource(<span class="keyword">select</span>.From);</span><br><span class="line">        Expression <span class="keyword">where</span> = <span class="keyword">this</span>.Visit(<span class="keyword">select</span>.Where);</span><br><span class="line">        ReadOnlyCollection&lt;ColumnDeclaration&gt; columns = <span class="keyword">this</span>.VisitColumnDeclarations(<span class="keyword">select</span>.Columns);</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">from</span> != <span class="keyword">select</span>.From || <span class="keyword">where</span> != <span class="keyword">select</span>.Where || columns != <span class="keyword">select</span>.Columns) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> SelectExpression(<span class="keyword">select</span>.Type, <span class="keyword">select</span>.Alias, columns, <span class="keyword">from</span>, <span class="keyword">where</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">select</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">VisitSource</span>(<span class="params">Expression source</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.Visit(source);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">VisitProjection</span>(<span class="params">ProjectionExpression proj</span>)</span> &#123;</span><br><span class="line">        SelectExpression source = (SelectExpression)<span class="keyword">this</span>.Visit(proj.Source);</span><br><span class="line">        Expression projector = <span class="keyword">this</span>.Visit(proj.Projector);</span><br><span class="line">        <span class="keyword">if</span> (source != proj.Source || projector != proj.Projector) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> ProjectionExpression(source, projector);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> proj;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> ReadOnlyCollection&lt;ColumnDeclaration&gt; <span class="title">VisitColumnDeclarations</span>(<span class="params">ReadOnlyCollection&lt;ColumnDeclaration&gt; columns</span>)</span> &#123;</span><br><span class="line">        List&lt;ColumnDeclaration&gt; alternate = <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>, n = columns.Count; i &lt; n; i++) &#123;</span><br><span class="line">            ColumnDeclaration column = columns[i];</span><br><span class="line">            Expression e = <span class="keyword">this</span>.Visit(column.Expression);</span><br><span class="line">            <span class="keyword">if</span> (alternate == <span class="literal">null</span> &amp;&amp; e != column.Expression) &#123;</span><br><span class="line">                alternate = columns.Take(i).ToList();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (alternate != <span class="literal">null</span>) &#123;</span><br><span class="line">                alternate.Add(<span class="keyword">new</span> ColumnDeclaration(column.Name, e));</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (alternate != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> alternate.AsReadOnly();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> columns;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我现在真的觉得自己越来越屌了！</p><p>下面就是<code>QueryTranslator</code>闪亮登场的时候了。不再是整个将表达式树翻译成字符串的翻译器，而是处理不同任务的独立的模块，一个模块解释方法（比如<code>Queryable.Select</code>）的含义、绑定表达式树，另一个将得到的树转换为SQL文本。希望通过构造这个LINQ/SQL混合的的树能够解决这个漏洞。</p><p>下面是<code>QueryBinder</code>类的代码。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">QueryBinder</span> : <span class="title">ExpressionVisitor</span> &#123;</span><br><span class="line">    ColumnProjector columnProjector;</span><br><span class="line">    Dictionary&lt;ParameterExpression, Expression&gt; map;</span><br><span class="line">    <span class="built_in">int</span> aliasCount;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="title">QueryBinder</span>()</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.columnProjector = <span class="keyword">new</span> ColumnProjector(<span class="keyword">this</span>.CanBeColumn);</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="built_in">bool</span> <span class="title">CanBeColumn</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> expression.NodeType == (ExpressionType)DbExpressionType.Column;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">internal</span> Expression <span class="title">Bind</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.map = <span class="keyword">new</span> Dictionary&lt;ParameterExpression, Expression&gt;();</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.Visit(expression);</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> Expression <span class="title">StripQuotes</span>(<span class="params">Expression e</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (e.NodeType == ExpressionType.Quote) &#123;</span><br><span class="line">            e = ((UnaryExpression)e).Operand;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> e;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="built_in">string</span> <span class="title">GetNextAlias</span>()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;t&quot;</span> + (aliasCount++);</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> ProjectedColumns <span class="title">ProjectColumns</span>(<span class="params">Expression expression, <span class="built_in">string</span> newAlias, <span class="built_in">string</span> existingAlias</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.columnProjector.ProjectColumns(expression, newAlias, existingAlias);</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitMethodCall</span>(<span class="params">MethodCallExpression m</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (m.Method.DeclaringType == <span class="keyword">typeof</span>(Queryable) ||</span><br><span class="line">            m.Method.DeclaringType == <span class="keyword">typeof</span>(Enumerable)) &#123;</span><br><span class="line">            <span class="keyword">switch</span> (m.Method.Name) &#123;</span><br><span class="line">                <span class="keyword">case</span> <span class="string">&quot;Where&quot;</span>:</span><br><span class="line">                    <span class="keyword">return</span> <span class="keyword">this</span>.BindWhere(m.Type, m.Arguments[<span class="number">0</span>], (LambdaExpression)StripQuotes(m.Arguments[<span class="number">1</span>]));</span><br><span class="line">                <span class="keyword">case</span> <span class="string">&quot;Select&quot;</span>:</span><br><span class="line">                    <span class="keyword">return</span> <span class="keyword">this</span>.BindSelect(m.Type, m.Arguments[<span class="number">0</span>], (LambdaExpression)StripQuotes(m.Arguments[<span class="number">1</span>]));</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> NotSupportedException(<span class="built_in">string</span>.Format(<span class="string">&quot;The method &#x27;&#123;0&#125;&#x27; is not supported&quot;</span>, m.Method.Name));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">base</span>.VisitMethodCall(m);</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> Expression <span class="title">BindWhere</span>(<span class="params">Type resultType, Expression source, LambdaExpression predicate</span>)</span> &#123;</span><br><span class="line">        ProjectionExpression projection = (ProjectionExpression)<span class="keyword">this</span>.Visit(source);</span><br><span class="line">        <span class="keyword">this</span>.map[predicate.Parameters[<span class="number">0</span>]] = projection.Projector;</span><br><span class="line">        Expression <span class="keyword">where</span> = <span class="keyword">this</span>.Visit(predicate.Body);</span><br><span class="line">        <span class="built_in">string</span> <span class="keyword">alias</span> = <span class="keyword">this</span>.GetNextAlias();</span><br><span class="line">        ProjectedColumns pc = <span class="keyword">this</span>.ProjectColumns(projection.Projector, <span class="keyword">alias</span>, GetExistingAlias(projection.Source));</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> ProjectionExpression(</span><br><span class="line">            <span class="keyword">new</span> SelectExpression(resultType, <span class="keyword">alias</span>, pc.Columns, projection.Source, <span class="keyword">where</span>),</span><br><span class="line">            pc.Projector</span><br><span class="line">            );</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> Expression <span class="title">BindSelect</span>(<span class="params">Type resultType, Expression source, LambdaExpression selector</span>)</span> &#123;</span><br><span class="line">        ProjectionExpression projection = (ProjectionExpression)<span class="keyword">this</span>.Visit(source);</span><br><span class="line">        <span class="keyword">this</span>.map[selector.Parameters[<span class="number">0</span>]] = projection.Projector;</span><br><span class="line">        Expression expression = <span class="keyword">this</span>.Visit(selector.Body);</span><br><span class="line">        <span class="built_in">string</span> <span class="keyword">alias</span> = <span class="keyword">this</span>.GetNextAlias();</span><br><span class="line">        ProjectedColumns pc = <span class="keyword">this</span>.ProjectColumns(expression, <span class="keyword">alias</span>, GetExistingAlias(projection.Source));</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> ProjectionExpression(</span><br><span class="line">            <span class="keyword">new</span> SelectExpression(resultType, <span class="keyword">alias</span>, pc.Columns, projection.Source, <span class="literal">null</span>),</span><br><span class="line">            pc.Projector</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="built_in">string</span> <span class="title">GetExistingAlias</span>(<span class="params">Expression source</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">switch</span> ((DbExpressionType)source.NodeType) &#123;</span><br><span class="line">            <span class="keyword">case</span> DbExpressionType.Select:</span><br><span class="line">                <span class="keyword">return</span> ((SelectExpression)source).Alias;</span><br><span class="line">            <span class="keyword">case</span> DbExpressionType.Table:</span><br><span class="line">                <span class="keyword">return</span> ((TableExpression)source).Alias;</span><br><span class="line">            <span class="literal">default</span>:</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> InvalidOperationException(<span class="built_in">string</span>.Format(<span class="string">&quot;Invalid source node type &#x27;&#123;0&#125;&#x27;&quot;</span>, source.NodeType));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="built_in">bool</span> <span class="title">IsTable</span>(<span class="params"><span class="built_in">object</span> <span class="keyword">value</span></span>)</span> &#123;</span><br><span class="line">        IQueryable q = <span class="keyword">value</span> <span class="keyword">as</span> IQueryable;</span><br><span class="line">        <span class="keyword">return</span> q != <span class="literal">null</span> &amp;&amp; q.Expression.NodeType == ExpressionType.Constant;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="built_in">string</span> <span class="title">GetTableName</span>(<span class="params"><span class="built_in">object</span> table</span>)</span> &#123;</span><br><span class="line">        IQueryable tableQuery = (IQueryable)table;</span><br><span class="line">        Type rowType = tableQuery.ElementType;</span><br><span class="line">        <span class="keyword">return</span> rowType.Name;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="built_in">string</span> <span class="title">GetColumnName</span>(<span class="params">MemberInfo member</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> member.Name;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> Type <span class="title">GetColumnType</span>(<span class="params">MemberInfo member</span>)</span> &#123;</span><br><span class="line">        FieldInfo fi = member <span class="keyword">as</span> FieldInfo;</span><br><span class="line">        <span class="keyword">if</span> (fi != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> fi.FieldType;</span><br><span class="line">        &#125;</span><br><span class="line">        PropertyInfo pi = (PropertyInfo)member;</span><br><span class="line">        <span class="keyword">return</span> pi.PropertyType;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="title">IEnumerable</span>&lt;<span class="title">MemberInfo</span>&gt; <span class="title">GetMappedMembers</span>(<span class="params">Type rowType</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> rowType.GetFields().Cast&lt;MemberInfo&gt;();</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> ProjectionExpression <span class="title">GetTableProjection</span>(<span class="params"><span class="built_in">object</span> <span class="keyword">value</span></span>)</span> &#123;</span><br><span class="line">        IQueryable table = (IQueryable)<span class="keyword">value</span>;</span><br><span class="line">        <span class="built_in">string</span> tableAlias = <span class="keyword">this</span>.GetNextAlias();</span><br><span class="line">        <span class="built_in">string</span> selectAlias = <span class="keyword">this</span>.GetNextAlias();</span><br><span class="line">        List&lt;MemberBinding&gt; bindings = <span class="keyword">new</span> List&lt;MemberBinding&gt;();</span><br><span class="line">        List&lt;ColumnDeclaration&gt; columns = <span class="keyword">new</span> List&lt;ColumnDeclaration&gt;();</span><br><span class="line">        <span class="keyword">foreach</span> (MemberInfo mi <span class="keyword">in</span> <span class="keyword">this</span>.GetMappedMembers(table.ElementType)) &#123;</span><br><span class="line">            <span class="built_in">string</span> columnName = <span class="keyword">this</span>.GetColumnName(mi);</span><br><span class="line">            Type columnType = <span class="keyword">this</span>.GetColumnType(mi);</span><br><span class="line">            <span class="built_in">int</span> ordinal = columns.Count;</span><br><span class="line">            bindings.Add(Expression.Bind(mi, <span class="keyword">new</span> ColumnExpression(columnType, selectAlias, columnName, ordinal)));</span><br><span class="line">            columns.Add(<span class="keyword">new</span> ColumnDeclaration(columnName, <span class="keyword">new</span> ColumnExpression(columnType, tableAlias, columnName, ordinal)));</span><br><span class="line">        &#125;</span><br><span class="line">        Expression projector = Expression.MemberInit(Expression.New(table.ElementType), bindings);</span><br><span class="line">        Type resultType = <span class="keyword">typeof</span>(IEnumerable&lt;&gt;).MakeGenericType(table.ElementType);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> ProjectionExpression(</span><br><span class="line">            <span class="keyword">new</span> SelectExpression(</span><br><span class="line">                resultType,</span><br><span class="line">                selectAlias,</span><br><span class="line">                columns,</span><br><span class="line">                <span class="keyword">new</span> TableExpression(resultType, tableAlias, <span class="keyword">this</span>.GetTableName(table)),</span><br><span class="line">                <span class="literal">null</span></span><br><span class="line">            ),</span><br><span class="line">            projector</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitConstant</span>(<span class="params">ConstantExpression c</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">this</span>.IsTable(c.Value)) &#123;</span><br><span class="line">            <span class="keyword">return</span> GetTableProjection(c.Value);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> c;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitParameter</span>(<span class="params">ParameterExpression p</span>)</span> &#123;</span><br><span class="line">        Expression e;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">this</span>.map.TryGetValue(p, <span class="keyword">out</span> e)) &#123;</span><br><span class="line">            <span class="keyword">return</span> e;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> p;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitMemberAccess</span>(<span class="params">MemberExpression m</span>)</span> &#123;</span><br><span class="line">        Expression source = <span class="keyword">this</span>.Visit(m.Expression);</span><br><span class="line">        <span class="keyword">switch</span> (source.NodeType) &#123;</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.MemberInit:</span><br><span class="line">                MemberInitExpression min = (MemberInitExpression)source;</span><br><span class="line">                <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>, n = min.Bindings.Count; i &lt; n; i++) &#123;</span><br><span class="line">                    MemberAssignment assign = min.Bindings[i] <span class="keyword">as</span> MemberAssignment;</span><br><span class="line">                    <span class="keyword">if</span> (assign != <span class="literal">null</span> &amp;&amp; MembersMatch(assign.Member, m.Member)) &#123;</span><br><span class="line">                        <span class="keyword">return</span> assign.Expression;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.New:</span><br><span class="line">                NewExpression nex = (NewExpression)source;</span><br><span class="line">                <span class="keyword">if</span> (nex.Members != <span class="literal">null</span>) &#123;</span><br><span class="line">                    <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>, n = nex.Members.Count; i &lt; n; i++) &#123;</span><br><span class="line">                        <span class="keyword">if</span> (MembersMatch(nex.Members[i], m.Member)) &#123;</span><br><span class="line">                            <span class="keyword">return</span> nex.Arguments[i];</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (source == m.Expression) &#123;</span><br><span class="line">            <span class="keyword">return</span> m;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> MakeMemberAccess(source, m.Member);</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="built_in">bool</span> <span class="title">MembersMatch</span>(<span class="params">MemberInfo a, MemberInfo b</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (a == b) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (a <span class="keyword">is</span> MethodInfo &amp;&amp; b <span class="keyword">is</span> PropertyInfo) &#123;</span><br><span class="line">            <span class="keyword">return</span> a == ((PropertyInfo)b).GetGetMethod();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (a <span class="keyword">is</span> PropertyInfo &amp;&amp; b <span class="keyword">is</span> MethodInfo) &#123;</span><br><span class="line">            <span class="keyword">return</span> ((PropertyInfo)a).GetGetMethod() == b;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> Expression <span class="title">MakeMemberAccess</span>(<span class="params">Expression source, MemberInfo mi</span>)</span> &#123;</span><br><span class="line">        FieldInfo fi = mi <span class="keyword">as</span> FieldInfo;</span><br><span class="line">        <span class="keyword">if</span> (fi != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> Expression.Field(source, fi);</span><br><span class="line">        &#125;</span><br><span class="line">        PropertyInfo pi = (PropertyInfo)mi;</span><br><span class="line">        <span class="keyword">return</span> Expression.Property(source, pi);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>要注意这里的代码可比以前的<code>QueryTranslator</code>复杂多了。对<code>Where</code>和<code>Select</code>方法的翻译被分发到了两个独立的方法里面。它们不再产生文本，取而代之的是<code>ProjectionExpression</code>和<code>SelectExpression</code>的实例。<code>ColumnProjector</code>似乎做了一些更复杂的事情，我还没有放出它的代码，但是它也有很大的变化。这里还有些获得表和列的信息的帮助方法，其具体的实现要依靠一个完整的映射系统，留待以后解决，现在简单地使用类名和成员名。</p><p><code>GetTableProjection</code>是一个关键的方法，它用<code>SelectExpression</code>和<code>ProjectExpression</code>组装了一个取出表中所有数据的默认查询。这里不再使用&quot;<code>SELECT *</code>&quot;，默认的表投影是为域对象里面的所有成员一一赋值的<code>MemberInitExpression</code>。</p><p>另一个值得注意的变化是<code>VisitMemberAccess</code>方法。我不再只考虑参数节点的简单成员访问，还尝试解析成员访问的含义，返回这个成员翻译出来的子表达式。</p><p>这是具体的工作流程。当通过<code>GetTableProjection</code>方法将“表”常量翻译为表投影时，结果里包含了一个投影器表达式，它描述了如何通过表中的列来创建对象。当翻译到<code>Select</code>或<code>Where</code>方法时，往map中添加了一个从<code>LambdaExpression</code>的参数表达式到“上一次”查询的投影器的映射。对于第一个<code>Select</code>或<code>Where</code>的调用，这个投影器就是表投影中的投影器。这样，待会在<code>VisitParameter</code>方法中访问这个参数表达式时，就可直接将其替换为上一个投影器表达式。这样是可行的，因为节点是不可变的，因此可以在树上多次包含某棵子树。最后，在翻译成员访问的时候，参数表达式早已被替换成了语义等价的投影器表达式。这个投影器表达式有可能是new或者member-init节点，所以我只需在它上面找出能替换掉此成员访问节点的子表达式即可。通常，都能找到一个在表投影中定义的<code>ColumnExpression</code>。但是如果上次Select操作产生了嵌套对象的话，也有可能找到另一个new或者member-init表达式，这样的话，随后的成员访问操作会从这个表达式中查找子表达式，如此反复。</p><p>呼，有好多东西要消化，我自己都还没完全理解。下面是与之前完全不同的<code>ColumnProjector</code>类，看代码。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="keyword">sealed</span> <span class="keyword">class</span> <span class="title">ProjectedColumns</span> &#123;</span><br><span class="line">    Expression projector;</span><br><span class="line">    ReadOnlyCollection&lt;ColumnDeclaration&gt; columns;</span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="title">ProjectedColumns</span>(<span class="params">Expression projector, ReadOnlyCollection&lt;ColumnDeclaration&gt; columns</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.projector = projector;</span><br><span class="line">        <span class="keyword">this</span>.columns = columns;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">internal</span> Expression Projector &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.projector; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">internal</span> ReadOnlyCollection&lt;ColumnDeclaration&gt; Columns &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.columns; &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">ColumnProjector</span> : <span class="title">DbExpressionVisitor</span> &#123;</span><br><span class="line">    Nominator nominator;</span><br><span class="line">    Dictionary&lt;ColumnExpression, ColumnExpression&gt; map;</span><br><span class="line">    List&lt;ColumnDeclaration&gt; columns;</span><br><span class="line">    HashSet&lt;<span class="built_in">string</span>&gt; columnNames;</span><br><span class="line">    HashSet&lt;Expression&gt; candidates;</span><br><span class="line">    <span class="built_in">string</span> existingAlias;</span><br><span class="line">    <span class="built_in">string</span> newAlias;</span><br><span class="line">    <span class="built_in">int</span> iColumn;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="title">ColumnProjector</span>(<span class="params">Func&lt;Expression, <span class="built_in">bool</span>&gt; fnCanBeColumn</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.nominator = <span class="keyword">new</span> Nominator(fnCanBeColumn);</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">internal</span> ProjectedColumns <span class="title">ProjectColumns</span>(<span class="params">Expression expression, <span class="built_in">string</span> newAlias, <span class="built_in">string</span> existingAlias</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.map = <span class="keyword">new</span> Dictionary&lt;ColumnExpression, ColumnExpression&gt;();</span><br><span class="line">        <span class="keyword">this</span>.columns = <span class="keyword">new</span> List&lt;ColumnDeclaration&gt;();</span><br><span class="line">        <span class="keyword">this</span>.columnNames = <span class="keyword">new</span> HashSet&lt;<span class="built_in">string</span>&gt;();</span><br><span class="line">        <span class="keyword">this</span>.newAlias = newAlias;</span><br><span class="line">        <span class="keyword">this</span>.existingAlias = existingAlias;</span><br><span class="line">        <span class="keyword">this</span>.candidates = <span class="keyword">this</span>.nominator.Nominate(expression);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> ProjectedColumns(<span class="keyword">this</span>.Visit(expression), <span class="keyword">this</span>.columns.AsReadOnly());</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">Visit</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">this</span>.candidates.Contains(expression)) &#123;</span><br><span class="line">            <span class="keyword">if</span> (expression.NodeType == (ExpressionType)DbExpressionType.Column) &#123;</span><br><span class="line">                ColumnExpression column = (ColumnExpression)expression;</span><br><span class="line">                ColumnExpression mapped;</span><br><span class="line">                <span class="keyword">if</span> (<span class="keyword">this</span>.map.TryGetValue(column, <span class="keyword">out</span> mapped)) &#123;</span><br><span class="line">                    <span class="keyword">return</span> mapped;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (<span class="keyword">this</span>.existingAlias == column.Alias) &#123;</span><br><span class="line">                    <span class="built_in">int</span> ordinal = <span class="keyword">this</span>.columns.Count;</span><br><span class="line">                    <span class="built_in">string</span> columnName = <span class="keyword">this</span>.GetUniqueColumnName(column.Name);</span><br><span class="line">                    <span class="keyword">this</span>.columns.Add(<span class="keyword">new</span> ColumnDeclaration(columnName, column));</span><br><span class="line">                    mapped = <span class="keyword">new</span> ColumnExpression(column.Type, <span class="keyword">this</span>.newAlias, columnName, ordinal);</span><br><span class="line">                    <span class="keyword">this</span>.map[column] = mapped;</span><br><span class="line">                    <span class="keyword">this</span>.columnNames.Add(columnName);</span><br><span class="line">                    <span class="keyword">return</span> mapped;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// must be referring to outer scope</span></span><br><span class="line">                <span class="keyword">return</span> column;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="built_in">string</span> columnName = <span class="keyword">this</span>.GetNextColumnName();</span><br><span class="line">                <span class="built_in">int</span> ordinal = <span class="keyword">this</span>.columns.Count;</span><br><span class="line">                <span class="keyword">this</span>.columns.Add(<span class="keyword">new</span> ColumnDeclaration(columnName, expression));</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">new</span> ColumnExpression(expression.Type, <span class="keyword">this</span>.newAlias, columnName, ordinal);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">base</span>.Visit(expression);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="built_in">bool</span> <span class="title">IsColumnNameInUse</span>(<span class="params"><span class="built_in">string</span> name</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.columnNames.Contains(name);</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="built_in">string</span> <span class="title">GetUniqueColumnName</span>(<span class="params"><span class="built_in">string</span> name</span>)</span> &#123;</span><br><span class="line">        <span class="built_in">string</span> baseName = name;</span><br><span class="line">        <span class="built_in">int</span> suffix = <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">while</span> (<span class="keyword">this</span>.IsColumnNameInUse(name)) &#123;</span><br><span class="line">            name = baseName + (suffix++);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> name;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="built_in">string</span> <span class="title">GetNextColumnName</span>()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.GetUniqueColumnName(<span class="string">&quot;c&quot;</span> + (iColumn++));</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">class</span> <span class="title">Nominator</span> : <span class="title">DbExpressionVisitor</span> &#123;</span><br><span class="line">        Func&lt;Expression, <span class="built_in">bool</span>&gt; fnCanBeColumn;</span><br><span class="line">        <span class="built_in">bool</span> isBlocked;</span><br><span class="line">        HashSet&lt;Expression&gt; candidates;</span><br><span class="line"> </span><br><span class="line">        <span class="function"><span class="keyword">internal</span> <span class="title">Nominator</span>(<span class="params">Func&lt;Expression, <span class="built_in">bool</span>&gt; fnCanBeColumn</span>)</span> &#123;</span><br><span class="line">            <span class="keyword">this</span>.fnCanBeColumn = fnCanBeColumn;</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        <span class="function"><span class="keyword">internal</span> HashSet&lt;Expression&gt; <span class="title">Nominate</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">            <span class="keyword">this</span>.candidates = <span class="keyword">new</span> HashSet&lt;Expression&gt;();</span><br><span class="line">            <span class="keyword">this</span>.isBlocked = <span class="literal">false</span>;</span><br><span class="line">            <span class="keyword">this</span>.Visit(expression);</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">this</span>.candidates;</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">Visit</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (expression != <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="built_in">bool</span> saveIsBlocked = <span class="keyword">this</span>.isBlocked;</span><br><span class="line">                <span class="keyword">this</span>.isBlocked = <span class="literal">false</span>;</span><br><span class="line">                <span class="keyword">base</span>.Visit(expression);</span><br><span class="line">                <span class="keyword">if</span> (!<span class="keyword">this</span>.isBlocked) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (<span class="keyword">this</span>.fnCanBeColumn(expression)) &#123;</span><br><span class="line">                        <span class="keyword">this</span>.candidates.Add(expression);</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">else</span> &#123;</span><br><span class="line">                        <span class="keyword">this</span>.isBlocked = <span class="literal">true</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">this</span>.isBlocked |= saveIsBlocked;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> expression;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>ColumnProjector</code>类不再拼接Select命令的文本，也不再将选择器表达式转换为从<code>DataReader</code>构建对象的函数。但是其实做的事情和以前也差不多。它产生用来创建<code>SelectExpression</code>节点的<code>ColumnDeclaration</code>的list对象，将选择器表达式转换为引用了list中的这些列的投影器表达式。</p><p>那它是如何工作的呢？就现在来看，我对这个类可能有点过度设计，但是在以后这样子会比较方便。在我介绍它的工作原理之前，让我们先想想它需要干什么。</p><p>给定选择器表达式，我需要找出里面与SQL Select子句中的列声明直接相关的子表达式。这个很简单，只需要找出绑定之后树上剩余的列引用(<code>ColumnExpression</code>)就好了。当然，这意味着表达式“a + b”会被视为两个列引用，一个是“a”，一个是“b”，“+”操作则会留在新创建的投影器表达式里面。这样确实可行，但是能不能将整个“a + ｂ”表达式视为一列呢？这样的话，计算的操作就会在SQL server中执行，而不是在创建结果对象期间由本地执行。如果在这个Select操作后面有一个Where操作引用到了这个表达式的话，计算操作就无论如何都必须在服务器中执行了。现在先忽略还不能翻译“+”操作的问题吧，你可以看到，找出列引用表达式、生成投影器表达式的问题，与找出可预处理的独立子树的问题是相似的。</p><p><code>Evaluator</code>使用了两次遍历，第一次遍历找出所有可本地计算的节点，第二次遍历自顶向下选中第一次遍历时找出的节点，然后计算选中的“最大”子树的值。找出表达式中的列引用(<code>ColumnExpression</code>)与找出最大子树实际上是一个相同的问题，唯一的不同只是查找条件的差异。不过这次我不是要计算所找出的子树的值，而是要1)将子树放进新的查询的<code>SelectExpression</code>的列声明中，2)将子树替换为对新的查询的列的引用，从而创建一个投影器。</p><p>检查代码你会发现这里有个<code>Evaluator</code>类中没有的复杂性。如果列声明真的是基于更复杂的子表达式的话，我就应该给它们生成一个列别名。</p><p>好了，现在我已经创建了混合表达式树，并且已经很好地生成了投影器表达式，但我还是要生成SQL文本，否则前面的东西都白做了。所以我将<code>QueryTranslator</code>中生成文本的代码提了出来，创建了一个新的类<code>QueryFormatter</code>，它全权负责将一颗表达式树转换为文本。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">QueryFormatter</span> : <span class="title">DbExpressionVisitor</span> &#123;</span><br><span class="line">    StringBuilder sb;</span><br><span class="line">    <span class="built_in">int</span> indent = <span class="number">2</span>;</span><br><span class="line">    <span class="built_in">int</span> depth;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="title">QueryFormatter</span>()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="built_in">string</span> <span class="title">Format</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.sb = <span class="keyword">new</span> StringBuilder();</span><br><span class="line">        <span class="keyword">this</span>.Visit(expression);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.sb.ToString();</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">protected</span> <span class="built_in">enum</span> Identation &#123;</span><br><span class="line">        Same,</span><br><span class="line">        Inner,</span><br><span class="line">        Outer</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">internal</span> <span class="built_in">int</span> IdentationWidth &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.indent; &#125;</span><br><span class="line">        <span class="keyword">set</span> &#123; <span class="keyword">this</span>.indent = <span class="keyword">value</span>; &#125;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">AppendNewLine</span>(<span class="params">Identation style</span>)</span> &#123;</span><br><span class="line">        sb.AppendLine();</span><br><span class="line">        <span class="keyword">if</span> (style == Identation.Inner) &#123;</span><br><span class="line">            <span class="keyword">this</span>.depth++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (style == Identation.Outer) &#123;</span><br><span class="line">            <span class="keyword">this</span>.depth--;</span><br><span class="line">            System.Diagnostics.Debug.Assert(<span class="keyword">this</span>.depth &gt;= <span class="number">0</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>, n = <span class="keyword">this</span>.depth * <span class="keyword">this</span>.indent; i &lt; n; i++) &#123;</span><br><span class="line">            sb.Append(<span class="string">&quot; &quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitMethodCall</span>(<span class="params">MethodCallExpression m</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> NotSupportedException(<span class="built_in">string</span>.Format(<span class="string">&quot;The method &#x27;&#123;0&#125;&#x27; is not supported&quot;</span>, m.Method.Name));</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitUnary</span>(<span class="params">UnaryExpression u</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">switch</span> (u.NodeType) &#123;</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.Not:</span><br><span class="line">                sb.Append(<span class="string">&quot; NOT &quot;</span>);</span><br><span class="line">                <span class="keyword">this</span>.Visit(u.Operand);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="literal">default</span>:</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> NotSupportedException(<span class="built_in">string</span>.Format(<span class="string">&quot;The unary operator &#x27;&#123;0&#125;&#x27; is not supported&quot;</span>, u.NodeType));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> u;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitBinary</span>(<span class="params">BinaryExpression b</span>)</span> &#123;</span><br><span class="line">        sb.Append(<span class="string">&quot;(&quot;</span>);</span><br><span class="line">        <span class="keyword">this</span>.Visit(b.Left);</span><br><span class="line">        <span class="keyword">switch</span> (b.NodeType) &#123;</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.And:</span><br><span class="line">                sb.Append(<span class="string">&quot; AND &quot;</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.Or:</span><br><span class="line">                sb.Append(<span class="string">&quot; OR&quot;</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.Equal:</span><br><span class="line">                sb.Append(<span class="string">&quot; = &quot;</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.NotEqual:</span><br><span class="line">                sb.Append(<span class="string">&quot; &lt;&gt; &quot;</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.LessThan:</span><br><span class="line">                sb.Append(<span class="string">&quot; &lt; &quot;</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.LessThanOrEqual:</span><br><span class="line">                sb.Append(<span class="string">&quot; &lt;= &quot;</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.GreaterThan:</span><br><span class="line">                sb.Append(<span class="string">&quot; &gt; &quot;</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.GreaterThanOrEqual:</span><br><span class="line">                sb.Append(<span class="string">&quot; &gt;= &quot;</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="literal">default</span>:</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> NotSupportedException(<span class="built_in">string</span>.Format(<span class="string">&quot;The binary operator &#x27;&#123;0&#125;&#x27; is not supported&quot;</span>, b.NodeType));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">this</span>.Visit(b.Right);</span><br><span class="line">        sb.Append(<span class="string">&quot;)&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> b;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitConstant</span>(<span class="params">ConstantExpression c</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (c.Value == <span class="literal">null</span>) &#123;</span><br><span class="line">            sb.Append(<span class="string">&quot;NULL&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">switch</span> (Type.GetTypeCode(c.Value.GetType())) &#123;</span><br><span class="line">                <span class="keyword">case</span> TypeCode.Boolean:</span><br><span class="line">                    sb.Append(((<span class="built_in">bool</span>)c.Value) ? <span class="number">1</span> : <span class="number">0</span>);</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                <span class="keyword">case</span> TypeCode.String:</span><br><span class="line">                    sb.Append(<span class="string">&quot;&#x27;&quot;</span>);</span><br><span class="line">                    sb.Append(c.Value);</span><br><span class="line">                    sb.Append(<span class="string">&quot;&#x27;&quot;</span>);</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                <span class="keyword">case</span> TypeCode.Object:</span><br><span class="line">                    <span class="keyword">throw</span> <span class="keyword">new</span> NotSupportedException(<span class="built_in">string</span>.Format(<span class="string">&quot;The constant for &#x27;&#123;0&#125;&#x27; is not supported&quot;</span>, c.Value));</span><br><span class="line">                <span class="literal">default</span>:</span><br><span class="line">                    sb.Append(c.Value);</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> c;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitColumn</span>(<span class="params">ColumnExpression column</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (!<span class="built_in">string</span>.IsNullOrEmpty(column.Alias)) &#123;</span><br><span class="line">            sb.Append(column.Alias);</span><br><span class="line">            sb.Append(<span class="string">&quot;.&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        sb.Append(column.Name);</span><br><span class="line">        <span class="keyword">return</span> column;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitSelect</span>(<span class="params">SelectExpression <span class="keyword">select</span></span>)</span> &#123;</span><br><span class="line">        sb.Append(<span class="string">&quot;SELECT &quot;</span>);</span><br><span class="line">        <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>, n = <span class="keyword">select</span>.Columns.Count; i &lt; n; i++) &#123;</span><br><span class="line">            ColumnDeclaration column = <span class="keyword">select</span>.Columns[i];</span><br><span class="line">            <span class="keyword">if</span> (i &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                sb.Append(<span class="string">&quot;, &quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            ColumnExpression c = <span class="keyword">this</span>.Visit(column.Expression) <span class="keyword">as</span> ColumnExpression;</span><br><span class="line">            <span class="keyword">if</span> (c == <span class="literal">null</span> || c.Name != <span class="keyword">select</span>.Columns[i].Name) &#123;</span><br><span class="line">                sb.Append(<span class="string">&quot; AS &quot;</span>);</span><br><span class="line">                sb.Append(column.Name);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">select</span>.From != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">this</span>.AppendNewLine(Identation.Same);</span><br><span class="line">            sb.Append(<span class="string">&quot;FROM &quot;</span>);</span><br><span class="line">            <span class="keyword">this</span>.VisitSource(<span class="keyword">select</span>.From);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">select</span>.Where != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">this</span>.AppendNewLine(Identation.Same);</span><br><span class="line">            sb.Append(<span class="string">&quot;WHERE &quot;</span>);</span><br><span class="line">            <span class="keyword">this</span>.Visit(<span class="keyword">select</span>.Where);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">select</span>;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitSource</span>(<span class="params">Expression source</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">switch</span> ((DbExpressionType)source.NodeType) &#123;</span><br><span class="line">            <span class="keyword">case</span> DbExpressionType.Table:</span><br><span class="line">                TableExpression table = (TableExpression)source;</span><br><span class="line">                sb.Append(table.Name);</span><br><span class="line">                sb.Append(<span class="string">&quot; AS &quot;</span>);</span><br><span class="line">                sb.Append(table.Alias);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> DbExpressionType.Select:</span><br><span class="line">                SelectExpression <span class="keyword">select</span> = (SelectExpression)source;</span><br><span class="line">                sb.Append(<span class="string">&quot;(&quot;</span>);</span><br><span class="line">                <span class="keyword">this</span>.AppendNewLine(Identation.Inner);</span><br><span class="line">                <span class="keyword">this</span>.Visit(<span class="keyword">select</span>);</span><br><span class="line">                <span class="keyword">this</span>.AppendNewLine(Identation.Outer);</span><br><span class="line">                sb.Append(<span class="string">&quot;)&quot;</span>);</span><br><span class="line">                sb.Append(<span class="string">&quot; AS &quot;</span>);</span><br><span class="line">                sb.Append(<span class="keyword">select</span>.Alias);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="literal">default</span>:</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> InvalidOperationException(<span class="string">&quot;Select source is not valid type&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> source;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>除了添加了输出新的<code>SelectExpression</code>节点的逻辑之外，我还添加了格式化的逻辑，以支持换行和缩进。现在是不是比较特别了？</p><p>当然，最后还是要以一个构造结果对象的<code>LambdaExpression</code>结束。我们之前是通过<code>ColumnProjector</code>类来获得这个lambda表达式的，但现在它的工作是生成SQL语义投影器，而不是生成创建结果对象的投影器。所以我们需要进一步的转换，我建了一个新的类<code>ProjectionBuilder</code>来做这件事。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">ProjectionBuilder</span> : <span class="title">DbExpressionVisitor</span> &#123;</span><br><span class="line">    ParameterExpression row;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> MethodInfo miGetValue;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="title">ProjectionBuilder</span>()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (miGetValue == <span class="literal">null</span>) &#123;</span><br><span class="line">            miGetValue = <span class="keyword">typeof</span>(ProjectionRow).GetMethod(<span class="string">&quot;GetValue&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">internal</span> LambdaExpression <span class="title">Build</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.row = Expression.Parameter(<span class="keyword">typeof</span>(ProjectionRow), <span class="string">&quot;row&quot;</span>);</span><br><span class="line">        Expression body = <span class="keyword">this</span>.Visit(expression);</span><br><span class="line">        <span class="keyword">return</span> Expression.Lambda(body, <span class="keyword">this</span>.row);</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitColumn</span>(<span class="params">ColumnExpression column</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Expression.Convert(Expression.Call(<span class="keyword">this</span>.row, miGetValue, Expression.Constant(column.Ordinal)), column.Type);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个类简单地做了<code>ColumnProjector</code>之前的工作，不过得益于<code>QueryBinder</code>中的更好的绑定逻辑，它现在直接就知道应该将哪些节点替换为数据读取表达式。</p><p>很幸运，我们不用重写<code>ProjectionReader</code>，它还是像以前那样工作。我要做的是摆脱<code>ObjectReader</code>，因为我们现在始终都会有一个投影器表达式，在<code>QueryBinder</code>中每次翻译到“表”常量时都会创建一个。</p><p>现在就是将前面讲的东西都用上的最后一步了。下面是重写的<code>DbQueryProvider</code>的代码。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">DbQueryProvider</span> : <span class="title">QueryProvider</span> &#123;</span><br><span class="line">    DbConnection connection;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">DbQueryProvider</span>(<span class="params">DbConnection connection</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.connection = connection;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">string</span> <span class="title">GetQueryText</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.Translate(expression).CommandText;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">object</span> <span class="title">Execute</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        TranslateResult result = <span class="keyword">this</span>.Translate(expression);</span><br><span class="line">        Delegate projector = result.Projector.Compile();</span><br><span class="line"> </span><br><span class="line">        DbCommand cmd = <span class="keyword">this</span>.connection.CreateCommand();</span><br><span class="line">        cmd.CommandText = result.CommandText;</span><br><span class="line">        DbDataReader reader = cmd.ExecuteReader();</span><br><span class="line"> </span><br><span class="line">        Type elementType = TypeSystem.GetElementType(expression.Type);</span><br><span class="line">        <span class="keyword">return</span> Activator.CreateInstance(</span><br><span class="line">            <span class="keyword">typeof</span>(ProjectionReader&lt;&gt;).MakeGenericType(elementType),</span><br><span class="line">            BindingFlags.Instance | BindingFlags.NonPublic, <span class="literal">null</span>,</span><br><span class="line">            <span class="keyword">new</span> <span class="built_in">object</span>[] &#123; reader, projector &#125;,</span><br><span class="line">            <span class="literal">null</span></span><br><span class="line">            );</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">TranslateResult</span> &#123;</span><br><span class="line">        <span class="keyword">internal</span> <span class="built_in">string</span> CommandText;</span><br><span class="line">        <span class="keyword">internal</span> LambdaExpression Projector;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> TranslateResult <span class="title">Translate</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        expression = Evaluator.PartialEval(expression);</span><br><span class="line">        ProjectionExpression proj = (ProjectionExpression)<span class="keyword">new</span> QueryBinder().Bind(expression);</span><br><span class="line">        <span class="built_in">string</span> commandText = <span class="keyword">new</span> QueryFormatter().Format(proj.Source);</span><br><span class="line">        LambdaExpression projector = <span class="keyword">new</span> ProjectionBuilder().Build(proj.Projector);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> TranslateResult &#123; CommandText = commandText, Projector = projector &#125;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>它和以前有很大的不同。<code>Translate</code>方法包含了很多步骤，它调用新增的各种访问器，以及<code>Execute</code>方法也不再创建<code>ObjectReader</code>对象，因为现在始终都有一个投影器。</p><p>现在，给出下面的查询：</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> query = db.Customers.Select(c =&gt; <span class="keyword">new</span> &#123;</span><br><span class="line">                            Name = c.ContactName,</span><br><span class="line">                            Location = <span class="keyword">new</span> &#123;</span><br><span class="line">                                City = c.City,</span><br><span class="line">                                Country = c.Country</span><br><span class="line">                            &#125; </span><br><span class="line">                        &#125;)</span><br><span class="line">                        .Where(x =&gt; x.Location.City == city);</span><br></pre></td></tr></table></figure><p>执行成功，产生如下输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">Query:</span><br><span class="line">SELECT t2.ContactName, t2.City, t2.Country</span><br><span class="line">FROM (</span><br><span class="line">  SELECT t1.ContactName, t1.City, t1.Country</span><br><span class="line">  FROM (</span><br><span class="line">    SELECT t0.ContactName, t0.City, t0.Country, t0.CustomerID, t0.Phone</span><br><span class="line">    FROM Customers AS t0</span><br><span class="line">  ) AS t1</span><br><span class="line">) AS t2</span><br><span class="line">WHERE (t2.City = &#x27;London&#x27;)</span><br><span class="line"> </span><br><span class="line">&#123; Name = Thomas Hardy, Location = &#123; City = London, Country = UK &#125; &#125;</span><br><span class="line">&#123; Name = Victoria Ashworth, Location = &#123; City = London, Country = UK &#125; &#125;</span><br><span class="line">&#123; Name = Elizabeth Brown, Location = &#123; City = London, Country = UK &#125; &#125;</span><br><span class="line">&#123; Name = Ann Devon, Location = &#123; City = London, Country = UK &#125; &#125;</span><br><span class="line">&#123; Name = Simon Crowther, Location = &#123; City = London, Country = UK &#125; &#125;</span><br><span class="line">&#123; Name = Hari Kumar, Location = &#123; City = London, Country = UK &#125; &#125;</span><br></pre></td></tr></table></figure><p>更好看的查询，更好看的结果，而且现在无论有多少个<code>Select</code>或者<code>Where</code>方法，无论里面的投影有多复杂它都能运行良好。</p><p>在我指出下一个漏洞之前，至少应该让你们好好消化一下。<i class="emoji emoji-smile"></i></p><p>下次见！</p><p><a href="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Components.PostAttachments/00/04/21/35/21/Query5.zip">Query5.zip</a></p>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;英文原文是&lt;a href=&quot;https://social.msdn.microsoft.com/profile/matt%20warren%20-%20msft/&quot; title=&quot;Matt Warren&quot;&gt;Matt Warren&lt;/a&gt;发表在MSDN Blogs的系列文章之一，英文渣渣，翻译&lt;strong&gt;不供参考&lt;/strong&gt;，请直接&lt;a href=&quot;http://blogs.msdn.com/b/mattwar/archive/2007/08/03/linq-building-an-iqueryable-provider-part-v.aspx&quot;&gt;看原文&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在前面四篇文章里面，我构建了一个LINQ IQueryable提供程序，它可将&lt;code&gt;Queryable.Where&lt;/code&gt;和&lt;code&gt;Queryable.Select&lt;/code&gt;两个标准查询操作符翻译成SQL，并通过ADO送到数据库中去执行。虽然已经做得很不错，但是这个提供程序还是有一些漏洞，而且我还没有提到其他的查询操作，比如OrderBy和Join等等。如果认为用户写出的查询都像我的demo一样这么理想化的话，你可能就会掉进大坑里去。&lt;/p&gt;</summary>
    
    
    
    
    <category term="C♯" scheme="https://www.liuwj.me/tags/C-Sharp/"/>
    
  </entry>
  
  <entry>
    <title>「译」LINQ: Building an IQueryable Provider - Part IV: Select</title>
    <link href="https://www.liuwj.me/posts/linq-building-an-iqueryable-provider-part-iv/"/>
    <id>https://www.liuwj.me/posts/linq-building-an-iqueryable-provider-part-iv/</id>
    <published>2016-02-03T00:00:00.000Z</published>
    <updated>2026-03-01T02:56:55.950Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>英文原文是<a href="https://social.msdn.microsoft.com/profile/matt%20warren%20-%20msft/" title="Matt Warren">Matt Warren</a>发表在MSDN Blogs的系列文章之一，英文渣渣，翻译<strong>不供参考</strong>，请直接<a href="http://blogs.msdn.com/b/mattwar/archive/2007/08/02/linq-building-an-iqueryable-provider-part-iv.aspx">看原文</a>。</p></blockquote><p>我是个完美主义者，我做了一个仅仅可以将Where方法翻译为SQL的LINQ提供程序，它可以执行查询并且将结果转换为对象，但我觉得还不够完美，相信你们也这么认为。你们也许想知道从一个简单的示例程序演变成一个成熟的ORM系统的所有细节。但是我并不会做到这个程度，即便如此，我还是觉得，我可以通过介绍如何实现Select操作来覆盖到一些通用的知识点，以方便你编写自己的提供程序。</p><span id="more"></span><h2 id="implementing-select"><a class="markdownIt-Anchor" href="#implementing-select"></a> Implementing Select</h2><p>与Where操作比起来，翻译Select操作可没那么容易。我现在说的不是那种从十列里面选出五列的SQL操作，而是将数据转换为任何你想要的形式的LINQ Select操作。LINQ Select操作中的选择器函数可以是你能想象到的任何转换表达式，里面可能会有对象构造器、初始化器、条件语句、二元运算符、方法调用等等。这么多东西要如何翻译为SQL，更别说还要从返回的结果里面重新构造出对象的结构？</p><p>幸运的是，我并不会真的这样做。为什么呢？因为要写的代码太多吗？实际上是因为本来已经就有代码帮我们处理了大部分的事情，所以我才不需要熬夜奋战。我不用自己写，因为用户在写查询的时候就已经把转换代码写出来了。</p><p>选择器函数就是构造结果的代码。在LINQ to Objects中，选择器函数会被真正地调用，从而产生结果，那为何在我的查询提供程序中就要不一样呢？</p><p>啊哈，先别急。当然，如果选择器函数是可执行代码而不是表达式树的话，那是最好的，即便它是可执行代码，它也只是个将对象转换为另一个对象的函数罢了。可是我并没有要转换的对象啊。我有一个<code>DbDataReader</code>，它带有许多字段数据，可它是拿来生成最终的对象的，我现在还没有要拿来转换的对象啊。</p><p>当然，也许你能自己想出一个好的解决方案，将前面的<code>ObjectReader</code>与LINQ to Objects版本的Select操作结合起来，取出所有数据，转换成另外一种形式。但是这对时间和空间都是巨大的浪费。我们不应该取出所有的数据，我们只应该取需要拿来产生结果的数据就够了。这真是个进退两难的局面。</p><p>幸运的是，问题仍然很简单。只需要将原有的选择器函数转换成我们需要的样子就可以了。我们需要什么样子的呢？我们需要它直接从<code>DbDataReader</code>中读取数据。好了，我们可以将这个问题中的<code>DataReader</code>抽象出来，提供一个<code>GetValue</code>方法让选择器函数获得数据。对，我知道<code>DataReader</code>里面已经有了一个这样的方法，但是它有个缺点，就是可能会返回<code>DbNull</code>。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title">ProjectionRow</span> &#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="built_in">object</span> <span class="title">GetValue</span>(<span class="params"><span class="built_in">int</span> index</span>)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>所以，我们有了一个简单的抽象基类，它代表了一行数据。如果我们的选择器表达式是从这里通过调用<code>GetValue</code>方法来获得数据，然后接上一个<code>Expression.Convert</code>强转操作的话，我真的是做梦都会笑醒。</p><p>让我们看看预处理选择器表达式的代码吧。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">ColumnProjection</span> &#123;</span><br><span class="line">    <span class="keyword">internal</span> <span class="built_in">string</span> Columns;</span><br><span class="line">    <span class="keyword">internal</span> Expression Selector;</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">ColumnProjector</span> : <span class="title">ExpressionVisitor</span> &#123;</span><br><span class="line">    StringBuilder sb;</span><br><span class="line">    <span class="built_in">int</span> iColumn;</span><br><span class="line">    ParameterExpression row;</span><br><span class="line">    <span class="keyword">static</span> MethodInfo miGetValue;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="title">ColumnProjector</span>()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (miGetValue == <span class="literal">null</span>) &#123;</span><br><span class="line">            miGetValue = <span class="keyword">typeof</span>(ProjectionRow).GetMethod(<span class="string">&quot;GetValue&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">internal</span> ColumnProjection <span class="title">ProjectColumns</span>(<span class="params">Expression expression, ParameterExpression row</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.sb = <span class="keyword">new</span> StringBuilder();</span><br><span class="line">        <span class="keyword">this</span>.row = row;</span><br><span class="line">        Expression selector = <span class="keyword">this</span>.Visit(expression);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> ColumnProjection &#123; Columns = <span class="keyword">this</span>.sb.ToString(), Selector = selector &#125;;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitMemberAccess</span>(<span class="params">MemberExpression m</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (m.Expression != <span class="literal">null</span> &amp;&amp; m.Expression.NodeType == ExpressionType.Parameter) &#123;</span><br><span class="line">            <span class="keyword">if</span> (<span class="keyword">this</span>.sb.Length &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">this</span>.sb.Append(<span class="string">&quot;, &quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">this</span>.sb.Append(m.Member.Name);</span><br><span class="line">            <span class="keyword">return</span> Expression.Convert(Expression.Call(<span class="keyword">this</span>.row, miGetValue, Expression.Constant(iColumn++)), m.Type);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">base</span>.VisitMemberAccess(m);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面的当然不是所有的代码。<code>ColumnProjector</code>是一个表达式访问器，它遍历表达式树，将列引用转换为调用<code>GetValue</code>方法获得单个数据的表达式。那<code>GetValue</code>方法又是通过什么来调用的呢？通过一个名为“row”的参数表达式，它的类型就是我刚刚定义的抽象类<code>ProjectionRow</code>。我不仅重建了一个选择器表达式，我还要把它放在一个以<code>ProjectionRow</code>为参数的lambda表达式的body中。这样我就能调用<code>LambdaExpression.Compile</code>方法将这个lambda表达式转换为委托。</p><p>注意这个表达式访问器还构造了一个SQL的select子句。通过这个类，我既可以将<code>Query.Select</code>中的查询表达式转换为处理查询结果的函数，又能得到SQL命令中的select子句。</p><p>让我们来看看怎么使用这个类吧，下面是修改后的<code>QueryTranslator</code>（仅给出相关内容）。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">TranslateResult</span> &#123;</span><br><span class="line">    <span class="keyword">internal</span> <span class="built_in">string</span> CommandText;</span><br><span class="line">    <span class="keyword">internal</span> LambdaExpression Prsojector;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">QueryTranslator</span> : <span class="title">ExpressionVisitor</span> &#123;</span><br><span class="line">    StringBuilder sb;</span><br><span class="line">    ParameterExpression row;</span><br><span class="line">    ColumnProjection projection;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="title">QueryTranslator</span>()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">internal</span> TranslateResult <span class="title">Translate</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.sb = <span class="keyword">new</span> StringBuilder();</span><br><span class="line">        <span class="keyword">this</span>.row = Expression.Parameter(<span class="keyword">typeof</span>(ProjectionRow), <span class="string">&quot;row&quot;</span>);</span><br><span class="line">        <span class="keyword">this</span>.Visit(expression);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> TranslateResult &#123;</span><br><span class="line">            CommandText = <span class="keyword">this</span>.sb.ToString(),</span><br><span class="line">            Projector = <span class="keyword">this</span>.projection != <span class="literal">null</span> ? Expression.Lambda(<span class="keyword">this</span>.projection.Selector, <span class="keyword">this</span>.row) : <span class="literal">null</span></span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitMethodCall</span>(<span class="params">MethodCallExpression m</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (m.Method.DeclaringType == <span class="keyword">typeof</span>(Queryable)) &#123;</span><br><span class="line">            <span class="keyword">if</span> (m.Method.Name == <span class="string">&quot;Where&quot;</span>) &#123;</span><br><span class="line">                sb.Append(<span class="string">&quot;SELECT * FROM (&quot;</span>);</span><br><span class="line">                <span class="keyword">this</span>.Visit(m.Arguments[<span class="number">0</span>]);</span><br><span class="line">                sb.Append(<span class="string">&quot;) AS T WHERE &quot;</span>);</span><br><span class="line">                LambdaExpression lambda = (LambdaExpression)StripQuotes(m.Arguments[<span class="number">1</span>]);</span><br><span class="line">                <span class="keyword">this</span>.Visit(lambda.Body);</span><br><span class="line">                <span class="keyword">return</span> m;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> (m.Method.Name == <span class="string">&quot;Select&quot;</span>) &#123;</span><br><span class="line">                LambdaExpression lambda = (LambdaExpression)StripQuotes(m.Arguments[<span class="number">1</span>]);</span><br><span class="line">                ColumnProjection projection = <span class="keyword">new</span> ColumnProjector().ProjectColumns(lambda.Body, <span class="keyword">this</span>.row);</span><br><span class="line">                sb.Append(<span class="string">&quot;SELECT &quot;</span>);</span><br><span class="line">                sb.Append(projection.Columns);</span><br><span class="line">                sb.Append(<span class="string">&quot; FROM (&quot;</span>);</span><br><span class="line">                <span class="keyword">this</span>.Visit(m.Arguments[<span class="number">0</span>]);</span><br><span class="line">                sb.Append(<span class="string">&quot;) AS T &quot;</span>);</span><br><span class="line">                <span class="keyword">this</span>.projection = projection;</span><br><span class="line">                <span class="keyword">return</span> m;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> NotSupportedException(<span class="built_in">string</span>.Format(<span class="string">&quot;The method &#x27;&#123;0&#125;&#x27; is not supported&quot;</span>, m.Method.Name));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    . . .</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如你所见，<code>QueryTranslator</code>现在处理了Select方法，它就像Where方法一样构建一条SQL SELECT语句。但是它还保存了最后一个<code>ColumnProjection</code>对象（调用<code>ProjectColumns</code>方法的结果），在<code>TranslateResult</code>对象中以lambda表达式的形式返回新构建的选择器表达式。</p><p>现在我们只需要一个<code>ObjectReader</code>类，它使用这个lambda表达式来处理数据，而不是像之前那样仅仅只是创建一个对象。</p><p>看下面的代码。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">ProjectionReader</span>&lt;<span class="title">T</span>&gt; : <span class="title">IEnumerable</span>&lt;<span class="title">T</span>&gt;, <span class="title">IEnumerable</span> &#123;</span><br><span class="line">    Enumerator enumerator;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="title">ProjectionReader</span>(<span class="params">DbDataReader reader, Func&lt;ProjectionRow, T&gt; projector</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.enumerator = <span class="keyword">new</span> Enumerator(reader, projector);</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> IEnumerator&lt;T&gt; <span class="title">GetEnumerator</span>()</span> &#123;</span><br><span class="line">        Enumerator e = <span class="keyword">this</span>.enumerator;</span><br><span class="line">        <span class="keyword">if</span> (e == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> InvalidOperationException(<span class="string">&quot;Cannot enumerate more than once&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">this</span>.enumerator = <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">return</span> e;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    IEnumerator IEnumerable.GetEnumerator() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.GetEnumerator();</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">class</span> <span class="title">Enumerator</span> : <span class="title">ProjectionRow</span>, <span class="title">IEnumerator</span>&lt;<span class="title">T</span>&gt;, <span class="title">IEnumerator</span>, <span class="title">IDisposable</span> &#123;</span><br><span class="line">        DbDataReader reader;</span><br><span class="line">        T current;</span><br><span class="line">        Func&lt;ProjectionRow, T&gt; projector;</span><br><span class="line"> </span><br><span class="line">        <span class="function"><span class="keyword">internal</span> <span class="title">Enumerator</span>(<span class="params">DbDataReader reader, Func&lt;ProjectionRow, T&gt; projector</span>)</span> &#123;</span><br><span class="line">            <span class="keyword">this</span>.reader = reader;</span><br><span class="line">            <span class="keyword">this</span>.projector = projector;</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">object</span> <span class="title">GetValue</span>(<span class="params"><span class="built_in">int</span> index</span>)</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (index &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">if</span> (<span class="keyword">this</span>.reader.IsDBNull(index)) &#123;</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="keyword">return</span> <span class="keyword">this</span>.reader.GetValue(index);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> IndexOutOfRangeException();</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        <span class="keyword">public</span> T Current &#123;</span><br><span class="line">            <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.current; &#125;</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        <span class="built_in">object</span> IEnumerator.Current &#123;</span><br><span class="line">            <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.current; &#125;</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="built_in">bool</span> <span class="title">MoveNext</span>()</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (<span class="keyword">this</span>.reader.Read()) &#123;</span><br><span class="line">                <span class="keyword">this</span>.current = <span class="keyword">this</span>.projector(<span class="keyword">this</span>);</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Reset</span>()</span> &#123;</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Dispose</span>()</span> &#123;</span><br><span class="line">            <span class="keyword">this</span>.reader.Dispose();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>ProjectionReader</code>类与Part II中的<code>ObjectReader</code>类十分相似，只是去除了使用各个字段来创建对象的逻辑，替换成了一个名为<code>projector</code>的委托的调用。这就是我们重建的选择器表达式编译出来的委托。</p><p>记得吗，我们重建的选择器表达式是以<code>ProjectionRow</code>为参数的。现在你可以看到<code>ProjectionReader</code>里面的<code>Enumerator</code>就实现了<code>ProjectionRow</code>。这是件好事，因为它是这里唯一一个直接访问了<code>DbDataReader</code>的类，并且我们在调用委托的时候还可以很方便地将this作为参数传进去就好了。</p><p>似乎所有组件都已经没问题了，现在让我们把它们组装到<code>DbQueryProvider</code>中去。</p><p>下面是新的provider的代码：</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">DbQueryProvider</span> : <span class="title">QueryProvider</span> &#123;</span><br><span class="line">    DbConnection connection;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">DbQueryProvider</span>(<span class="params">DbConnection connection</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.connection = connection;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">string</span> <span class="title">GetQueryText</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.Translate(expression).CommandText;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">object</span> <span class="title">Execute</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        TranslateResult result = <span class="keyword">this</span>.Translate(expression);</span><br><span class="line"> </span><br><span class="line">        DbCommand cmd = <span class="keyword">this</span>.connection.CreateCommand();</span><br><span class="line">        cmd.CommandText = result.CommandText;</span><br><span class="line">        DbDataReader reader = cmd.ExecuteReader();</span><br><span class="line"> </span><br><span class="line">        Type elementType = TypeSystem.GetElementType(expression.Type);</span><br><span class="line">        <span class="keyword">if</span> (result.Projector != <span class="literal">null</span>) &#123;</span><br><span class="line">            Delegate projector = result.Projector.Compile();</span><br><span class="line">            <span class="keyword">return</span> Activator.CreateInstance(</span><br><span class="line">                <span class="keyword">typeof</span>(ProjectionReader&lt;&gt;).MakeGenericType(elementType),</span><br><span class="line">                BindingFlags.Instance | BindingFlags.NonPublic, <span class="literal">null</span>,</span><br><span class="line">                <span class="keyword">new</span> <span class="built_in">object</span>[] &#123; reader, projector &#125;,</span><br><span class="line">                <span class="literal">null</span></span><br><span class="line">                );</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> Activator.CreateInstance(</span><br><span class="line">                <span class="keyword">typeof</span>(ObjectReader&lt;&gt;).MakeGenericType(elementType),</span><br><span class="line">                BindingFlags.Instance | BindingFlags.NonPublic, <span class="literal">null</span>,</span><br><span class="line">                <span class="keyword">new</span> <span class="built_in">object</span>[] &#123; reader &#125;,</span><br><span class="line">                <span class="literal">null</span></span><br><span class="line">                );</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> TranslateResult <span class="title">Translate</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        expression = Evaluator.PartialEval(expression);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> QueryTranslator().Translate(expression);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对<code>Translate</code>方法的调用返回了我需要的所有东西，我只需要再调用<code>Compile</code>方法将lambda表达式转换为委托就可以。注意我仍然需要保留<code>ObjectReader</code>类，它会在查询中没有Select操作的时候用到。</p><p>现在来试试最后的结果如何吧。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">string</span> city = <span class="string">&quot;London&quot;</span>;</span><br><span class="line"><span class="keyword">var</span> query = db.Customers.Where(c =&gt; c.City == city)</span><br><span class="line">              .Select(c =&gt; <span class="keyword">new</span> &#123;Name = c.ContactName, Phone = c.Phone&#125;);</span><br><span class="line">Console.WriteLine(<span class="string">&quot;Query:\n&#123;0&#125;\n&quot;</span>, query);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> list = query.ToList();</span><br><span class="line"><span class="keyword">foreach</span> (<span class="keyword">var</span> item <span class="keyword">in</span> list) &#123;</span><br><span class="line">    Console.WriteLine(<span class="string">&quot;&#123;0&#125;&quot;</span>, item);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>执行上面的代码，输出结果如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Query:</span><br><span class="line">SELECT ContactName, Phone FROM (SELECT * FROM (SELECT * FROM Customers) AS T WHERE (City = &#x27;London&#x27;)) AS T</span><br><span class="line">&#123; Name = Thomas Hardy, Phone = (171) 555-7788 &#125;</span><br><span class="line">&#123; Name = Victoria Ashworth, Phone = (171) 555-1212 &#125;</span><br><span class="line">&#123; Name = Elizabeth Brown, Phone = (171) 555-2282 &#125;</span><br><span class="line">&#123; Name = Ann Devon, Phone = (171) 555-0297 &#125;</span><br><span class="line">&#123; Name = Simon Crowther, Phone = (171) 555-7733 &#125;</span><br><span class="line">&#123; Name = Hari Kumar, Phone = (171) 555-1717 &#125;</span><br></pre></td></tr></table></figure><p>看，我没有再返回所有数据了，这正是我想要的。翻译后的选择器表达式转换成了一个委托，这个委托包含了“new xxx”的匿名类型初始化器，调用了<code>GetValue</code>方法从<code>DataReader</code>中读取数据保存到返回的对象中，不需要再使用反射对每个字段赋值了。我们的查询提供程序越来越好了，你一定觉得我们应该已经完成了，这个提供程序好屌！还有什么是没做的吗？</p><p>我们还有许多的事情要做。即使有了Select，即使它真的能运行良好，这个解决方案还是有一些漏洞，要修补的话还要对代码进行大改。</p><p>幸运的是，对我来说，这才是好玩的部分，Part V中再见。</p><p>Matt.</p>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;英文原文是&lt;a href=&quot;https://social.msdn.microsoft.com/profile/matt%20warren%20-%20msft/&quot; title=&quot;Matt Warren&quot;&gt;Matt Warren&lt;/a&gt;发表在MSDN Blogs的系列文章之一，英文渣渣，翻译&lt;strong&gt;不供参考&lt;/strong&gt;，请直接&lt;a href=&quot;http://blogs.msdn.com/b/mattwar/archive/2007/08/02/linq-building-an-iqueryable-provider-part-iv.aspx&quot;&gt;看原文&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我是个完美主义者，我做了一个仅仅可以将Where方法翻译为SQL的LINQ提供程序，它可以执行查询并且将结果转换为对象，但我觉得还不够完美，相信你们也这么认为。你们也许想知道从一个简单的示例程序演变成一个成熟的ORM系统的所有细节。但是我并不会做到这个程度，即便如此，我还是觉得，我可以通过介绍如何实现Select操作来覆盖到一些通用的知识点，以方便你编写自己的提供程序。&lt;/p&gt;</summary>
    
    
    
    
    <category term="C♯" scheme="https://www.liuwj.me/tags/C-Sharp/"/>
    
  </entry>
  
  <entry>
    <title>「译」LINQ: Building an IQueryable Provider - Part III: Local variable references</title>
    <link href="https://www.liuwj.me/posts/linq-building-an-iqueryable-provider-part-iii/"/>
    <id>https://www.liuwj.me/posts/linq-building-an-iqueryable-provider-part-iii/</id>
    <published>2016-02-01T00:00:00.000Z</published>
    <updated>2026-03-01T02:56:55.950Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>英文原文是<a href="https://social.msdn.microsoft.com/profile/matt%20warren%20-%20msft/" title="Matt Warren">Matt Warren</a>发表在MSDN Blogs的系列文章之一，英文渣渣，翻译<strong>不供参考</strong>，请直接<a href="http://blogs.msdn.com/b/mattwar/archive/2007/08/01/linq-building-an-iqueryable-provider-part-iii.aspx">看原文</a>。</p></blockquote><p>第三部分？难道上篇文章还没有讲完吗？我不是做了一个可以翻译和执行SQL命令并且返回一个对象序列的提供程序了吗？</p><p>确实如此，但是也仅仅如此而已。我写的那个提供程序的功能实在太弱，它只支持一种查询操作符与少量比较运算符。然而，真正的查询提供程序必须要提供更多的查询操作与更复杂的交互方式。我的查询提供程序甚至还不支持将数据投影为其他形式。</p><h2 id="translating-local-variable-references"><a class="markdownIt-Anchor" href="#translating-local-variable-references"></a> Translating Local Variable References</h2><p>你知道当查询里面引用了局部变量的时候会发生什么吗？不知道？</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">string</span> city = <span class="string">&quot;London&quot;</span>;</span><br><span class="line"><span class="keyword">var</span> query = db.Customers.Where(c =&gt; c.City == city);</span><br></pre></td></tr></table></figure><p>去试试翻译上面这句查询的时候会出现什么情况吧，我等着你的结果。</p><span id="more"></span><p>靠，抛出了一个异常，“The member ‘city’ is not supported.”，这是什么意思？我将“成员”City视为表中的一列，这个异常指的是局部变量city。但是为何局部变量也是一个“成员”呢？</p><p>让我们再看看对表达式树调用<code>ToString()</code>方法的结果。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Console.WriteLine(query.Expression.ToString());</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT * FROM Customers.Where(c =&gt; <span class="keyword">return</span> (c.City = <span class="keyword">value</span>(Sample.Program+&lt;&gt;c__DisplayClass0).city))</span><br></pre></td></tr></table></figure><p>啊哈，C♯编译器生成了一个类来保存被lambda表达式引用到的局部变量，这和匿名函数中引用到外部的局部变量的时候的处理是一致的。但是这个你早就知道了对吧？不知道？</p><p>算了，我们现在来为之前的提供程序添加支持局部变量引用的功能吧。也许我们能够识别出这些编译器生成的类型中的字段引用，那么要如何确定一个编译器生成的类型呢？通过类名？如果编译器改变了它们的命名怎么办？如果另一种语言里面是另一种模式怎么办？还有，我们关注的点仅仅只有局部变量吗？如果引用了作用域范围中的成员变量呢？它们在表达式树中并不是单纯的值，它们可以是引用了成员变量所指向的实例的一个constant节点，也可以是访问某个对象的成员的MemberAccess节点。你能够仅仅通过反射就识别出constant节点所引用的成员变量并且得到它们的值吗？也许可以，但是万一编译器生成了一个更复杂的类型呢？</p><p>好吧，我要给出的是一个通用的解决方案，它转化了编译器生成的表达式树，使之更像我指出这些问题之前的样子，让人容易接受。</p><p>我真正想做的是将树上可以计算出值的子树替换成所计算出来的值。如果能做到的话，查询翻译器就只需要处理这些值就好了。谢天谢地，我已经有一个现成的<code>ExpressionVisitor</code>类，我可以用它实现一个简单的规则来判断哪些子树可以直接计算出值。</p><p>先看看下面的代码，我待会会解释它的工作原理。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title">Evaluator</span> &#123;</span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> Performs evaluation &amp; replacement of independent sub-trees</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;param name=&quot;expression&quot;&gt;</span>The root of the expression tree.<span class="doctag">&lt;/param&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;param name=&quot;fnCanBeEvaluated&quot;&gt;</span>A function that decides whether a given expression node can be part of the local function.<span class="doctag">&lt;/param&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;returns&gt;</span>A new tree with sub-trees evaluated and replaced.<span class="doctag">&lt;/returns&gt;</span></span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Expression <span class="title">PartialEval</span>(<span class="params">Expression expression, Func&lt;Expression, <span class="built_in">bool</span>&gt; fnCanBeEvaluated</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> SubtreeEvaluator(<span class="keyword">new</span> Nominator(fnCanBeEvaluated).Nominate(expression)).Eval(expression);</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> Performs evaluation &amp; replacement of independent sub-trees</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;param name=&quot;expression&quot;&gt;</span>The root of the expression tree.<span class="doctag">&lt;/param&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;returns&gt;</span>A new tree with sub-trees evaluated and replaced.<span class="doctag">&lt;/returns&gt;</span></span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Expression <span class="title">PartialEval</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> PartialEval(expression, Evaluator.CanBeEvaluatedLocally);</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="built_in">bool</span> <span class="title">CanBeEvaluatedLocally</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> expression.NodeType != ExpressionType.Parameter;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> Evaluates &amp; replaces sub-trees when first candidate is reached (top-down)</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="keyword">class</span> <span class="title">SubtreeEvaluator</span>: <span class="title">ExpressionVisitor</span> &#123;</span><br><span class="line">        HashSet&lt;Expression&gt; candidates;</span><br><span class="line"> </span><br><span class="line">        <span class="function"><span class="keyword">internal</span> <span class="title">SubtreeEvaluator</span>(<span class="params">HashSet&lt;Expression&gt; candidates</span>)</span> &#123;</span><br><span class="line">            <span class="keyword">this</span>.candidates = candidates;</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        <span class="function"><span class="keyword">internal</span> Expression <span class="title">Eval</span>(<span class="params">Expression exp</span>)</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">this</span>.Visit(exp);</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">Visit</span>(<span class="params">Expression exp</span>)</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (exp == <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (<span class="keyword">this</span>.candidates.Contains(exp)) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.Evaluate(exp);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">base</span>.Visit(exp);</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        <span class="function"><span class="keyword">private</span> Expression <span class="title">Evaluate</span>(<span class="params">Expression e</span>)</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (e.NodeType == ExpressionType.Constant) &#123;</span><br><span class="line">                <span class="keyword">return</span> e;</span><br><span class="line">            &#125;</span><br><span class="line">            LambdaExpression lambda = Expression.Lambda(e);</span><br><span class="line">            Delegate fn = lambda.Compile();</span><br><span class="line">            <span class="keyword">return</span> Expression.Constant(fn.DynamicInvoke(<span class="literal">null</span>), e.Type);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;summary&gt;</span></span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> Performs bottom-up analysis to determine which nodes can possibly</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> be part of an evaluated sub-tree.</span></span><br><span class="line">    <span class="comment"><span class="doctag">///</span> <span class="doctag">&lt;/summary&gt;</span></span></span><br><span class="line">    <span class="keyword">class</span> <span class="title">Nominator</span> : <span class="title">ExpressionVisitor</span> &#123;</span><br><span class="line">        Func&lt;Expression, <span class="built_in">bool</span>&gt; fnCanBeEvaluated;</span><br><span class="line">        HashSet&lt;Expression&gt; candidates;</span><br><span class="line">        <span class="built_in">bool</span> cannotBeEvaluated;</span><br><span class="line"> </span><br><span class="line">        <span class="function"><span class="keyword">internal</span> <span class="title">Nominator</span>(<span class="params">Func&lt;Expression, <span class="built_in">bool</span>&gt; fnCanBeEvaluated</span>)</span> &#123;</span><br><span class="line">            <span class="keyword">this</span>.fnCanBeEvaluated = fnCanBeEvaluated;</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        <span class="function"><span class="keyword">internal</span> HashSet&lt;Expression&gt; <span class="title">Nominate</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">            <span class="keyword">this</span>.candidates = <span class="keyword">new</span> HashSet&lt;Expression&gt;();</span><br><span class="line">            <span class="keyword">this</span>.Visit(expression);</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">this</span>.candidates;</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">Visit</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (expression != <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="built_in">bool</span> saveCannotBeEvaluated = <span class="keyword">this</span>.cannotBeEvaluated;</span><br><span class="line">                <span class="keyword">this</span>.cannotBeEvaluated = <span class="literal">false</span>;</span><br><span class="line">                <span class="keyword">base</span>.Visit(expression);</span><br><span class="line">                <span class="keyword">if</span> (!<span class="keyword">this</span>.cannotBeEvaluated) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (<span class="keyword">this</span>.fnCanBeEvaluated(expression)) &#123;</span><br><span class="line">                        <span class="keyword">this</span>.candidates.Add(expression);</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">else</span> &#123;</span><br><span class="line">                        <span class="keyword">this</span>.cannotBeEvaluated = <span class="literal">true</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">this</span>.cannotBeEvaluated |= saveCannotBeEvaluated;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> expression;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>Evaluator</code>类暴露了一个静态方法<code>PartialEval</code>，你可以调用这个方法来计算你的表达式树中的子树，并将其替换为计算结果的constant节点。上面的代码做的事情大部分是将可以独立计算的最大子树找出来，而真正的计算过程并没有什么特别，因为子树可以通过<code>LambdaExpression.Compile</code>方法“编译”成委托然后执行。这些事情都是在<code>SubtreeVisitor.Evaluate</code>方法中发生的。</p><p>找出最大子树的过程分为两步。首先是在<code>Nominator</code>类中对表达式树进行自底向上的遍历，找出所有可以独立计算的子树，然后在<code>SubtreeEvaluator</code>类中进行自上而下的遍历，找出代表选中的子树的最高节点。</p><p><code>Nominator</code>以一个函数作为参数，你可以随意指定一个方法作为判断指定节点是否可独立计算的条件。默认的判断条件是除了<code>ExpressionType.Parameter</code>类型以外的所有节点都可以独立计算。另外，如果子节点不可独立计算那么父节点也不可独立计算。因此，parameter类型的节点的所有上游节点都不可独立计算，它们都会保留在树上，而剩余的其他节点都会被计算出结果并且替换成constant节点。</p><p>现在我就可以在任何翻译表达式的操作之前使用上面的类对表达式进行预处理了。幸运的是，我已经把翻译操作分解到了<code>DbQueryProvider</code>类的<code>Translate</code>方法里面。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">DbQueryProvider</span> : <span class="title">QueryProvider</span> &#123;</span><br><span class="line">    …</span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="built_in">string</span> <span class="title">Translate</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        expression = Evaluator.PartialEval(expression);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> QueryTranslator().Translate(expression);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>现在我们再试试执行下面的代码就能得到正确的结果了：</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">string</span> city = <span class="string">&quot;London&quot;</span>;</span><br><span class="line"><span class="keyword">var</span> query = db.Customers.Where(c =&gt; c.City == city);</span><br><span class="line"> </span><br><span class="line">Console.WriteLine(<span class="string">&quot;Query:\n&#123;0&#125;\n&quot;</span>, query);</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Query:</span><br><span class="line">SELECT * FROM (SELECT * FROM Customers) AS T WHERE (City = &#x27;London&#x27;)</span><br></pre></td></tr></table></figure><p>结果正是我们想要的，我们的查询提供程序又向前走了一步！</p><p>下篇文章我会实现Select操作。<i class="emoji emoji-smile"></i></p>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;英文原文是&lt;a href=&quot;https://social.msdn.microsoft.com/profile/matt%20warren%20-%20msft/&quot; title=&quot;Matt Warren&quot;&gt;Matt Warren&lt;/a&gt;发表在MSDN Blogs的系列文章之一，英文渣渣，翻译&lt;strong&gt;不供参考&lt;/strong&gt;，请直接&lt;a href=&quot;http://blogs.msdn.com/b/mattwar/archive/2007/08/01/linq-building-an-iqueryable-provider-part-iii.aspx&quot;&gt;看原文&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;第三部分？难道上篇文章还没有讲完吗？我不是做了一个可以翻译和执行SQL命令并且返回一个对象序列的提供程序了吗？&lt;/p&gt;
&lt;p&gt;确实如此，但是也仅仅如此而已。我写的那个提供程序的功能实在太弱，它只支持一种查询操作符与少量比较运算符。然而，真正的查询提供程序必须要提供更多的查询操作与更复杂的交互方式。我的查询提供程序甚至还不支持将数据投影为其他形式。&lt;/p&gt;
&lt;h2 id=&quot;translating-local-variable-references&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#translating-local-variable-references&quot;&gt;&lt;/a&gt; Translating Local Variable References&lt;/h2&gt;
&lt;p&gt;你知道当查询里面引用了局部变量的时候会发生什么吗？不知道？&lt;/p&gt;
&lt;figure class=&quot;highlight cs&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;built_in&quot;&gt;string&lt;/span&gt; city = &lt;span class=&quot;string&quot;&gt;&amp;quot;London&amp;quot;&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;var&lt;/span&gt; query = db.Customers.Where(c =&amp;gt; c.City == city);&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;去试试翻译上面这句查询的时候会出现什么情况吧，我等着你的结果。&lt;/p&gt;</summary>
    
    
    
    
    <category term="C♯" scheme="https://www.liuwj.me/tags/C-Sharp/"/>
    
  </entry>
  
  <entry>
    <title>「译」LINQ: Building an IQueryable Provider - Part II: Where and reusable Expression tree visitor</title>
    <link href="https://www.liuwj.me/posts/linq-building-an-iqueryable-provider-part-ii/"/>
    <id>https://www.liuwj.me/posts/linq-building-an-iqueryable-provider-part-ii/</id>
    <published>2016-01-16T00:00:00.000Z</published>
    <updated>2026-03-01T02:56:55.949Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>英文原文是<a href="https://social.msdn.microsoft.com/profile/matt%20warren%20-%20msft/" title="Matt Warren">Matt Warren</a>发表在MSDN Blogs的系列文章之一，英文渣渣，翻译<strong>不供参考</strong>，请直接<a href="http://blogs.msdn.com/b/mattwar/archive/2007/07/31/linq-building-an-iqueryable-provider-part-ii.aspx">看原文</a>。</p></blockquote><p>在上篇文章中，我们已经打好了基础，定义了可重用的<code>IQueryable</code>和<code>IQueryProvider</code>，它们分别是<code>Query&lt;T&gt;</code>类和<code>QueryProvider</code>类，现在我们来构建一个真正有用的提供程序。我之前说过，一个查询提供程序所做的事就是执行一些“代码”，这些“代码”使用表达式树而不是真正的IL语言来定义。当然，这并不一定是传统意义上的执行。比如说，LINQ to SQL就是将查询表达式翻译为SQL然后送到服务器中去执行的。</p><p>我下面给出的示例与LINQ to SQL有点类似，都是针对一个DAO provider对查询进行翻译和执行。但是，我要做个免责声明，在任何意义上，我给出的示例都不是一个完整的提供程序。我只会翻译<code>Where</code>操作，并且只支持在谓词中使用一个字段引用和一些简单的运算符，除此之外没有任何复杂的东西。以后我可能会扩展这个提供程序，但现在仅用于说明的目的。所以不要以为复制粘贴就能得到高质量的代码。</p><p>这个提供程序主要做两件事：</p><ol><li>将查询翻译为SQL命令</li><li>将执行命令得到的结果转换为对象</li></ol><span id="more"></span><h2 id="the-query-translator"><a class="markdownIt-Anchor" href="#the-query-translator"></a> The Query Translator</h2><p><code>QueryTranslator</code>简单地访问查询表达式树中的每个节点，然后用<code>StringBuilder</code>将支持的操作转换成文本。为了代码的清晰，我们假设有一个叫<code>ExpressionVisitor</code>的类，它定义了访问表达式节点的基本模式（我会在文章的结尾附上这个类的代码的，现在暂且将就一下）。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">QueryTranslator</span> : <span class="title">ExpressionVisitor</span> &#123;</span><br><span class="line">    StringBuilder sb;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="title">QueryTranslator</span>()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="built_in">string</span> <span class="title">Translate</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.sb = <span class="keyword">new</span> StringBuilder();</span><br><span class="line">        <span class="keyword">this</span>.Visit(expression);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.sb.ToString();</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> Expression <span class="title">StripQuotes</span>(<span class="params">Expression e</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (e.NodeType == ExpressionType.Quote) &#123;</span><br><span class="line">            e = ((UnaryExpression)e).Operand;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> e;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitMethodCall</span>(<span class="params">MethodCallExpression m</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (m.Method.DeclaringType == <span class="keyword">typeof</span>(Queryable) &amp;&amp; m.Method.Name == <span class="string">&quot;Where&quot;</span>) &#123;</span><br><span class="line">            sb.Append(<span class="string">&quot;SELECT * FROM (&quot;</span>);</span><br><span class="line">            <span class="keyword">this</span>.Visit(m.Arguments[<span class="number">0</span>]);</span><br><span class="line">            sb.Append(<span class="string">&quot;) AS T WHERE &quot;</span>);</span><br><span class="line">            LambdaExpression lambda = (LambdaExpression)StripQuotes(m.Arguments[<span class="number">1</span>]);</span><br><span class="line">            <span class="keyword">this</span>.Visit(lambda.Body);</span><br><span class="line">            <span class="keyword">return</span> m;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> NotSupportedException(<span class="built_in">string</span>.Format(<span class="string">&quot;The method &#x27;&#123;0&#125;&#x27; is not supported&quot;</span>, m.Method.Name));</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitUnary</span>(<span class="params">UnaryExpression u</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">switch</span> (u.NodeType) &#123;</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.Not:</span><br><span class="line">                sb.Append(<span class="string">&quot; NOT &quot;</span>);</span><br><span class="line">                <span class="keyword">this</span>.Visit(u.Operand);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="literal">default</span>:</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> NotSupportedException(<span class="built_in">string</span>.Format(<span class="string">&quot;The unary operator &#x27;&#123;0&#125;&#x27; is not supported&quot;</span>, u.NodeType));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> u;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitBinary</span>(<span class="params">BinaryExpression b</span>)</span> &#123;</span><br><span class="line">        sb.Append(<span class="string">&quot;(&quot;</span>);</span><br><span class="line">        <span class="keyword">this</span>.Visit(b.Left);</span><br><span class="line">        <span class="keyword">switch</span> (b.NodeType) &#123;</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.And:</span><br><span class="line">                sb.Append(<span class="string">&quot; AND &quot;</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.Or:</span><br><span class="line">                sb.Append(<span class="string">&quot; OR&quot;</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.Equal:</span><br><span class="line">                sb.Append(<span class="string">&quot; = &quot;</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.NotEqual:</span><br><span class="line">                sb.Append(<span class="string">&quot; &lt;&gt; &quot;</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.LessThan:</span><br><span class="line">                sb.Append(<span class="string">&quot; &lt; &quot;</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.LessThanOrEqual:</span><br><span class="line">                sb.Append(<span class="string">&quot; &lt;= &quot;</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.GreaterThan:</span><br><span class="line">                sb.Append(<span class="string">&quot; &gt; &quot;</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.GreaterThanOrEqual:</span><br><span class="line">                sb.Append(<span class="string">&quot; &gt;= &quot;</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="literal">default</span>:</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> NotSupportedException(<span class="built_in">string</span>.Format(<span class="string">&quot;The binary operator &#x27;&#123;0&#125;&#x27; is not supported&quot;</span>, b.NodeType));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">this</span>.Visit(b.Right);</span><br><span class="line">        sb.Append(<span class="string">&quot;)&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> b;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitConstant</span>(<span class="params">ConstantExpression c</span>)</span> &#123;</span><br><span class="line">        IQueryable q = c.Value <span class="keyword">as</span> IQueryable;</span><br><span class="line">        <span class="keyword">if</span> (q != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// assume constant nodes w/ IQueryables are table references</span></span><br><span class="line">            sb.Append(<span class="string">&quot;SELECT * FROM &quot;</span>);</span><br><span class="line">            sb.Append(q.ElementType.Name);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (c.Value == <span class="literal">null</span>) &#123;</span><br><span class="line">            sb.Append(<span class="string">&quot;NULL&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">switch</span> (Type.GetTypeCode(c.Value.GetType())) &#123;</span><br><span class="line">                <span class="keyword">case</span> TypeCode.Boolean:</span><br><span class="line">                    sb.Append(((<span class="built_in">bool</span>)c.Value) ? <span class="number">1</span> : <span class="number">0</span>);</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                <span class="keyword">case</span> TypeCode.String:</span><br><span class="line">                    sb.Append(<span class="string">&quot;&#x27;&quot;</span>);</span><br><span class="line">                    sb.Append(c.Value);</span><br><span class="line">                    sb.Append(<span class="string">&quot;&#x27;&quot;</span>);</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                <span class="keyword">case</span> TypeCode.Object:</span><br><span class="line">                    <span class="keyword">throw</span> <span class="keyword">new</span> NotSupportedException(<span class="built_in">string</span>.Format(<span class="string">&quot;The constant for &#x27;&#123;0&#125;&#x27; is not supported&quot;</span>, c.Value));</span><br><span class="line">                <span class="literal">default</span>:</span><br><span class="line">                    sb.Append(c.Value);</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> c;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">override</span> Expression <span class="title">VisitMemberAccess</span>(<span class="params">MemberExpression m</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (m.Expression != <span class="literal">null</span> &amp;&amp; m.Expression.NodeType == ExpressionType.Parameter) &#123;</span><br><span class="line">            sb.Append(m.Member.Name);</span><br><span class="line">            <span class="keyword">return</span> m;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> NotSupportedException(<span class="built_in">string</span>.Format(<span class="string">&quot;The member &#x27;&#123;0&#125;&#x27; is not supported&quot;</span>, m.Member.Name));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>你看，这里虽然没有多少东西，但是也相当复杂。我所支持的表达式树充其量就是具有两个参数的方法调用节点，这两个参数一个是调用源（argument 0），一个是谓词（argument 1）。看上面的<code>VisitMethodCall</code>方法，我显式处理了<code>Queryable.Where</code>方法，生成<code>SELECT * FROM (</code>，递归访问调用源然后拼接上<code>) AS T WHERE </code>，最后再访问谓词，这样就可以在调用源中以嵌套子查询的方式支持其他查询操作。我没有处理其他的查询操作，但是通过这种方式，也能优雅地处理多个连续的<code>Where</code>方法调用。表的别名可以随便起（我用了“T”），因为我没有生成任何对别名的引用。一个完备的提供程序当然会提供这个。</p><p>这里有个叫<code>StripQuotes</code>的帮助方法，它的作用是去除所给参数的所有<code>ExpressionType.Quotes</code>节点，以取得原本的lambda表达式。</p><p><code>VisitUnary</code>和<code>VisitBinary</code>方法比较直截了当，它们简单地插入所支持的一元或二元操作所对应的正确的SQL文本。有趣的是<code>VisitConstant</code>方法，在这个示例中，只有处于表达式树的根处的<code>IQueryable</code>对象才与实际的数据表有关联。我假设<code>Query&lt;T&gt;</code>类的实例的constant节点代表了递归到最后的实际的数据表，于是我将<code>SELECT * FROM</code>和表名拼接了上去，这里的表名只是简单地以<code>ElementType</code>的返回类型的名称来充当。其他类型的constant节点只是被处理为实际的常量，这些常量将被作为直接量拼接到SQL命令中，并没有任何防止SQL注入攻击的手段，而这是一个真正的提供程序必须做的事。</p><p>最后，<code>VisitMemberAccess</code>方法假定所有对字段或属性的访问都代表着SQL命令中对数据列的引用，假定字段名或属性名就是数据库中的列名。并没有任何的检查来确保这个一致性。给定一个类<code>Customers</code>，它的字段与Northwind示例数据库中的列完全匹配，查询翻译器生成SQL的方式如下。</p><p>对于查询：</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Query&lt;Customers&gt; customers = ...;</span><br><span class="line">IQueryable&lt;Customers&gt; q = customers.Where(c =&gt; c.City == <span class="string">&quot;London&quot;</span>);</span><br></pre></td></tr></table></figure><p>生成如下SQL:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> (<span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> Customers) <span class="keyword">AS</span> T <span class="keyword">WHERE</span> (City <span class="operator">=</span> ‘London’)</span><br></pre></td></tr></table></figure><h2 id="the-object-reader"><a class="markdownIt-Anchor" href="#the-object-reader"></a> The Object Reader</h2><p>对象读取器的作用是将SQL查询返回的结果转换为对象。我写了一个简单的类，它的构造方法以<code>DbDataReader</code>为参数，具有类型参数<code>T</code>，还实现了<code>IEnumerable&lt;T&gt;</code>接口。这里面也没有什么花哨的东西，只是使用反射来为类的字段赋值罢了。字段的名字必须与<code>DbDataReader</code>中的列名匹配，并且字段的类型也要与之兼容。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title">ObjectReader</span>&lt;<span class="title">T</span>&gt; : <span class="title">IEnumerable</span>&lt;<span class="title">T</span>&gt;, <span class="title">IEnumerable</span> <span class="keyword">where</span> <span class="title">T</span> : <span class="keyword">class</span>, <span class="title">new</span>() &#123;</span><br><span class="line">    Enumerator enumerator;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="title">ObjectReader</span>(<span class="params">DbDataReader reader</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.enumerator = <span class="keyword">new</span> Enumerator(reader);</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> IEnumerator&lt;T&gt; <span class="title">GetEnumerator</span>()</span> &#123;</span><br><span class="line">        Enumerator e = <span class="keyword">this</span>.enumerator;</span><br><span class="line">        <span class="keyword">if</span> (e == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> InvalidOperationException(<span class="string">&quot;Cannot enumerate more than once&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">this</span>.enumerator = <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">return</span> e;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    IEnumerator IEnumerable.GetEnumerator() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.GetEnumerator();</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="keyword">class</span> <span class="title">Enumerator</span> : <span class="title">IEnumerator</span>&lt;<span class="title">T</span>&gt;, <span class="title">IEnumerator</span>, <span class="title">IDisposable</span> &#123;</span><br><span class="line">        DbDataReader reader;</span><br><span class="line">        FieldInfo[] fields;</span><br><span class="line">        <span class="built_in">int</span>[] fieldLookup;</span><br><span class="line">        T current;</span><br><span class="line"> </span><br><span class="line">        <span class="function"><span class="keyword">internal</span> <span class="title">Enumerator</span>(<span class="params">DbDataReader reader</span>)</span> &#123;</span><br><span class="line">            <span class="keyword">this</span>.reader = reader;</span><br><span class="line">            <span class="keyword">this</span>.fields = <span class="keyword">typeof</span>(T).GetFields();</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        <span class="keyword">public</span> T Current &#123;</span><br><span class="line">            <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.current; &#125;</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        <span class="built_in">object</span> IEnumerator.Current &#123;</span><br><span class="line">            <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.current; &#125;</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="built_in">bool</span> <span class="title">MoveNext</span>()</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (<span class="keyword">this</span>.reader.Read()) &#123;</span><br><span class="line">                <span class="keyword">if</span> (<span class="keyword">this</span>.fieldLookup == <span class="literal">null</span>) &#123;</span><br><span class="line">                    <span class="keyword">this</span>.InitFieldLookup();</span><br><span class="line">                &#125;</span><br><span class="line">                T instance = <span class="keyword">new</span> T();</span><br><span class="line">                <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>, n = <span class="keyword">this</span>.fields.Length; i &lt; n; i++) &#123;</span><br><span class="line">                    <span class="built_in">int</span> index = <span class="keyword">this</span>.fieldLookup[i];</span><br><span class="line">                    <span class="keyword">if</span> (index &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">                        FieldInfo fi = <span class="keyword">this</span>.fields[i];</span><br><span class="line">                        <span class="keyword">if</span> (<span class="keyword">this</span>.reader.IsDBNull(index)) &#123;</span><br><span class="line">                            fi.SetValue(instance, <span class="literal">null</span>);</span><br><span class="line">                        &#125;</span><br><span class="line">                        <span class="keyword">else</span> &#123;</span><br><span class="line">                            fi.SetValue(instance, <span class="keyword">this</span>.reader.GetValue(index));</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">this</span>.current = instance;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Reset</span>()</span> &#123;</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">Dispose</span>()</span> &#123;</span><br><span class="line">            <span class="keyword">this</span>.reader.Dispose();</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">InitFieldLookup</span>()</span> &#123;</span><br><span class="line">            Dictionary&lt;<span class="built_in">string</span>, <span class="built_in">int</span>&gt; map = <span class="keyword">new</span> Dictionary&lt;<span class="built_in">string</span>, <span class="built_in">int</span>&gt;(StringComparer.InvariantCultureIgnoreCase);</span><br><span class="line">            <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>, n = <span class="keyword">this</span>.reader.FieldCount; i &lt; n; i++) &#123;</span><br><span class="line">                map.Add(<span class="keyword">this</span>.reader.GetName(i), i);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">this</span>.fieldLookup = <span class="keyword">new</span> <span class="built_in">int</span>[<span class="keyword">this</span>.fields.Length];</span><br><span class="line">            <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>, n = <span class="keyword">this</span>.fields.Length; i &lt; n; i++) &#123;</span><br><span class="line">                <span class="built_in">int</span> index;</span><br><span class="line">                <span class="keyword">if</span> (map.TryGetValue(<span class="keyword">this</span>.fields[i].Name, <span class="keyword">out</span> index)) &#123;</span><br><span class="line">                    <span class="keyword">this</span>.fieldLookup[i] = index;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="keyword">this</span>.fieldLookup[i] = <span class="number">-1</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>ObjectReader</code>类为从<code>DbDataReader</code>中读取出来的每一行数据创建一个<code>T</code>类型的对象，使用反射API<code>FieldInfo.SetValue</code>来给对象中的每一个字段赋值。<code>ObjectReader</code>对象被创建的时候会实例化一个内部类<code>Enumerator</code>的对象，<code>GetEnumerator</code>方法被调用的时候会返回这个枚举器。因为<code>DbDataReader</code>不能重置和再次运行，所以这个枚举器也只能被使用一次，第二次调用<code>GetEnumerator</code>会抛出一个异常。</p><p><code>ObjectReader</code>对字段并没有严格的排序，这是因为<code>QueryTranslator</code>使用<code>SELECT *</code>来拼接SQL，这是不可避免的，因为程序没有办法知道结果中的列的顺序。注意，一般来说不建议在生产代码中使用<code>SELECT *</code>，这里只是出于说明的目的。为了支持返回结果中不同的列顺序，准确的序列会在运行时从<code>DbDataReader</code>中读取到第一条数据时生成。<code>InitFieldLookup</code>函数会创建一个从列名到列序数的一个映射，然后构建一个从对象的字段到列序数的查找表<code>fieldLookup</code>。</p><h2 id="the-provider"><a class="markdownIt-Anchor" href="#the-provider"></a> The Provider</h2><p>有了上面的两个类和上篇文章中定义的类，现在已经可以很容易就把它们结合起来，写出一个真正的<code>IQueryable</code>LINQ提供程序。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">DbQueryProvider</span> : <span class="title">QueryProvider</span> &#123;</span><br><span class="line">    DbConnection connection;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">DbQueryProvider</span>(<span class="params">DbConnection connection</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.connection = connection;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">string</span> <span class="title">GetQueryText</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.Translate(expression);</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">object</span> <span class="title">Execute</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        DbCommand cmd = <span class="keyword">this</span>.connection.CreateCommand();</span><br><span class="line">        cmd.CommandText = <span class="keyword">this</span>.Translate(expression);</span><br><span class="line">        DbDataReader reader = cmd.ExecuteReader();</span><br><span class="line">        Type elementType = TypeSystem.GetElementType(expression.Type);</span><br><span class="line">        <span class="keyword">return</span> Activator.CreateInstance(</span><br><span class="line">            <span class="keyword">typeof</span>(ObjectReader&lt;&gt;).MakeGenericType(elementType),</span><br><span class="line">            BindingFlags.Instance | BindingFlags.NonPublic, <span class="literal">null</span>,</span><br><span class="line">            <span class="keyword">new</span> <span class="built_in">object</span>[] &#123; reader &#125;,</span><br><span class="line">            <span class="literal">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="built_in">string</span> <span class="title">Translate</span>(<span class="params">Expression expression</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> QueryTranslator().Translate(expression);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>GetQueryText</code>方法使用<code>QueryTranslator</code>来产生SQL命令，<code>Execute</code>方法使用<code>QueryTranslator</code>和<code>ObjectReader</code>来创建<code>DbCommand</code>对象、执行命令、返回<code>IEnumerable</code>类型的结果。</p><h2 id="trying-it-out"><a class="markdownIt-Anchor" href="#trying-it-out"></a> Trying it Out</h2><p>现在，我们已经有了一个提供程序，让我们来写个demo试试看。仿照LINQ to SQL的模式，我定义了一个对应于Customers表的类，一个保存了查询对象（根查询）的“Context”，和一个使用了它们的小程序。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Customers</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">string</span> CustomerID;</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">string</span> ContactName;</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">string</span> Phone;</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">string</span> City;</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">string</span> Country;</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Orders</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">int</span> OrderID;</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">string</span> CustomerID;</span><br><span class="line">    <span class="keyword">public</span> DateTime OrderDate;</span><br><span class="line">&#125;</span><br><span class="line"> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Northwind</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> Query&lt;Customers&gt; Customers;</span><br><span class="line">    <span class="keyword">public</span> Query&lt;Orders&gt; Orders;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Northwind</span>(<span class="params">DbConnection connection</span>)</span> &#123;</span><br><span class="line">        QueryProvider provider = <span class="keyword">new</span> DbQueryProvider(connection);</span><br><span class="line">        <span class="keyword">this</span>.Customers = <span class="keyword">new</span> Query&lt;Customers&gt;(provider);</span><br><span class="line">        <span class="keyword">this</span>.Orders = <span class="keyword">new</span> Query&lt;Orders&gt;(provider);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title">Program</span> &#123;</span><br><span class="line">    <span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">Main</span>(<span class="params"><span class="built_in">string</span>[] <span class="keyword">args</span></span>)</span> &#123;</span><br><span class="line">        <span class="built_in">string</span> constr = <span class="string">@&quot;…&quot;</span>;</span><br><span class="line">        <span class="keyword">using</span> (SqlConnection con = <span class="keyword">new</span> SqlConnection(constr)) &#123;</span><br><span class="line">            con.Open();</span><br><span class="line">            Northwind db = <span class="keyword">new</span> Northwind(con);</span><br><span class="line"> </span><br><span class="line">            IQueryable&lt;Customers&gt; query = </span><br><span class="line">                 db.Customers.Where(c =&gt; c.City == <span class="string">&quot;London&quot;</span>);</span><br><span class="line"> </span><br><span class="line">            Console.WriteLine(<span class="string">&quot;Query:\n&#123;0&#125;\n&quot;</span>, query);</span><br><span class="line"> </span><br><span class="line">            <span class="keyword">var</span> list = query.ToList();</span><br><span class="line">            <span class="keyword">foreach</span> (<span class="keyword">var</span> item <span class="keyword">in</span> list) &#123;</span><br><span class="line">                Console.WriteLine(<span class="string">&quot;Name: &#123;0&#125;&quot;</span>, item.ContactName);</span><br><span class="line">            &#125;</span><br><span class="line"> </span><br><span class="line">            Console.ReadLine();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行这个程序，会得到下面的输出（注意必须将上面的数据库连接串替换成你自己的）：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">Query:</span><br><span class="line">SELECT * FROM (SELECT * FROM Customers) AS T WHERE (City = &#x27;London&#x27;)</span><br><span class="line"></span><br><span class="line">Name: Thomas Hardy</span><br><span class="line">Name: Victoria Ashworth</span><br><span class="line">Name: Elizabeth Brown</span><br><span class="line">Name: Ann Devon</span><br><span class="line">Name: Simon Crowther</span><br><span class="line">Name: Hari Kumar</span><br></pre></td></tr></table></figure><p>Excellent，正是我们想要的，计划实现了，心里有点小激动呢。<i class="emoji emoji-smile"></i></p><p>就是你了皮卡丘，这就是一个LINQ<code>IQueryable</code>提供程序，起码算是一个粗糙的原型。当然你还可以在里面做更多的事情，处理各种各样的情况。</p><p>别急，还有更精彩的。<a href="http://www.liuwj.me/posts/linq-building-an-iqueryable-provider-part-iii/">查看Part III</a>。</p><h2 id="appendix-the-expression-visitor"><a class="markdownIt-Anchor" href="#appendix-the-expression-visitor"></a> APPENDIX – The Expression Visitor</h2><p>吊了这么久胃口，我感觉向我要<code>ExpressionVisitor</code>类的代码的人可能会比问我如何构建查询提供程序的人还要多。<code>System.Linq.Expressions</code>里面就有一个<code>ExpressionVisitor</code>类，但是它是internal的，所以尽管你很想直接用，但是并不能。如果你强烈要求的话说不定我们会在下个版本里面把它改成public。</p><p>我写的这个<code>ExpressionVisitor</code>使用了经典访问者模式。这里只有一个访问者类，用来将<code>Visit</code>方法的调用分派到与不同节点类型匹配的特定的<code>VisitXXX</code>方法。注意每个节点类型都会对应一个方法，比如二元运算节点就会被分派到<code>VisitBinary</code>方法。节点本身并不直接参与访问操作，它们仅仅被视为数据。这是因为访问者的数量是不限的，你也可以写一个自己的访问者类，这样可以让访问语义集中在访问者类中，避免其耦合到不同的节点类中去。对节点<code>XXX</code>的默认访问行为定义在基类的<code>VisitXXX</code>方法中。</p><p>每个<code>VisitXXX</code>方法都会返回一个节点。表达式树是不可变的，想改变表达式树就必须构建一颗全新的树。默认的<code>VisitXXX</code>方法在子树发生了变化的时候会创建一个新的节点，否则返回原来的节点。这样，如果你在树的深处（通过创建一个新节点）改变了一个节点，剩余的整棵树都会自动重新创建。</p><p>下面是源码，Enjoy。<i class="emoji emoji-smile"></i></p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title">ExpressionVisitor</span> &#123;</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="title">ExpressionVisitor</span>()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">Visit</span>(<span class="params">Expression exp</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (exp == <span class="literal">null</span>)</span><br><span class="line">            <span class="keyword">return</span> exp;</span><br><span class="line">        <span class="keyword">switch</span> (exp.NodeType) &#123;</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.Negate:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.NegateChecked:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.Not:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.Convert:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.ConvertChecked:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.ArrayLength:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.Quote:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.TypeAs:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.VisitUnary((UnaryExpression)exp);</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.Add:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.AddChecked:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.Subtract:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.SubtractChecked:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.Multiply:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.MultiplyChecked:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.Divide:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.Modulo:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.And:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.AndAlso:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.Or:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.OrElse:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.LessThan:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.LessThanOrEqual:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.GreaterThan:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.GreaterThanOrEqual:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.Equal:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.NotEqual:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.Coalesce:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.ArrayIndex:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.RightShift:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.LeftShift:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.ExclusiveOr:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.VisitBinary((BinaryExpression)exp);</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.TypeIs:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.VisitTypeIs((TypeBinaryExpression)exp);</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.Conditional:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.VisitConditional((ConditionalExpression)exp);</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.Constant:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.VisitConstant((ConstantExpression)exp);</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.Parameter:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.VisitParameter((ParameterExpression)exp);</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.MemberAccess:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.VisitMemberAccess((MemberExpression)exp);</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.Call:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.VisitMethodCall((MethodCallExpression)exp);</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.Lambda:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.VisitLambda((LambdaExpression)exp);</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.New:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.VisitNew((NewExpression)exp);</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.NewArrayInit:</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.NewArrayBounds:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.VisitNewArray((NewArrayExpression)exp);</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.Invoke:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.VisitInvocation((InvocationExpression)exp);</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.MemberInit:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.VisitMemberInit((MemberInitExpression)exp);</span><br><span class="line">            <span class="keyword">case</span> ExpressionType.ListInit:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.VisitListInit((ListInitExpression)exp);</span><br><span class="line">            <span class="literal">default</span>:</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> Exception(<span class="built_in">string</span>.Format(<span class="string">&quot;Unhandled expression type: &#x27;&#123;0&#125;&#x27;&quot;</span>, exp.NodeType));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> MemberBinding <span class="title">VisitBinding</span>(<span class="params">MemberBinding binding</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">switch</span> (binding.BindingType) &#123;</span><br><span class="line">            <span class="keyword">case</span> MemberBindingType.Assignment:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.VisitMemberAssignment((MemberAssignment)binding);</span><br><span class="line">            <span class="keyword">case</span> MemberBindingType.MemberBinding:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.VisitMemberMemberBinding((MemberMemberBinding)binding);</span><br><span class="line">            <span class="keyword">case</span> MemberBindingType.ListBinding:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.VisitMemberListBinding((MemberListBinding)binding);</span><br><span class="line">            <span class="literal">default</span>:</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> Exception(<span class="built_in">string</span>.Format(<span class="string">&quot;Unhandled binding type &#x27;&#123;0&#125;&#x27;&quot;</span>, binding.BindingType));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> ElementInit <span class="title">VisitElementInitializer</span>(<span class="params">ElementInit initializer</span>)</span> &#123;</span><br><span class="line">        ReadOnlyCollection&lt;Expression&gt; arguments = <span class="keyword">this</span>.VisitExpressionList(initializer.Arguments);</span><br><span class="line">        <span class="keyword">if</span> (arguments != initializer.Arguments) &#123;</span><br><span class="line">            <span class="keyword">return</span> Expression.ElementInit(initializer.AddMethod, arguments);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> initializer;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">VisitUnary</span>(<span class="params">UnaryExpression u</span>)</span> &#123;</span><br><span class="line">        Expression operand = <span class="keyword">this</span>.Visit(u.Operand);</span><br><span class="line">        <span class="keyword">if</span> (operand != u.Operand) &#123;</span><br><span class="line">            <span class="keyword">return</span> Expression.MakeUnary(u.NodeType, operand, u.Type, u.Method);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> u;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">VisitBinary</span>(<span class="params">BinaryExpression b</span>)</span> &#123;</span><br><span class="line">        Expression left = <span class="keyword">this</span>.Visit(b.Left);</span><br><span class="line">        Expression right = <span class="keyword">this</span>.Visit(b.Right);</span><br><span class="line">        Expression conversion = <span class="keyword">this</span>.Visit(b.Conversion);</span><br><span class="line">        <span class="keyword">if</span> (left != b.Left || right != b.Right || conversion != b.Conversion) &#123;</span><br><span class="line">            <span class="keyword">if</span> (b.NodeType == ExpressionType.Coalesce &amp;&amp; b.Conversion != <span class="literal">null</span>)</span><br><span class="line">                <span class="keyword">return</span> Expression.Coalesce(left, right, conversion <span class="keyword">as</span> LambdaExpression);</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                <span class="keyword">return</span> Expression.MakeBinary(b.NodeType, left, right, b.IsLiftedToNull, b.Method);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> b;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">VisitTypeIs</span>(<span class="params">TypeBinaryExpression b</span>)</span> &#123;</span><br><span class="line">        Expression expr = <span class="keyword">this</span>.Visit(b.Expression);</span><br><span class="line">        <span class="keyword">if</span> (expr != b.Expression) &#123;</span><br><span class="line">            <span class="keyword">return</span> Expression.TypeIs(expr, b.TypeOperand);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> b;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">VisitConstant</span>(<span class="params">ConstantExpression c</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> c;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">VisitConditional</span>(<span class="params">ConditionalExpression c</span>)</span> &#123;</span><br><span class="line">        Expression test = <span class="keyword">this</span>.Visit(c.Test);</span><br><span class="line">        Expression ifTrue = <span class="keyword">this</span>.Visit(c.IfTrue);</span><br><span class="line">        Expression ifFalse = <span class="keyword">this</span>.Visit(c.IfFalse);</span><br><span class="line">        <span class="keyword">if</span> (test != c.Test || ifTrue != c.IfTrue || ifFalse != c.IfFalse) &#123;</span><br><span class="line">            <span class="keyword">return</span> Expression.Condition(test, ifTrue, ifFalse);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> c;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">VisitParameter</span>(<span class="params">ParameterExpression p</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> p;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">VisitMemberAccess</span>(<span class="params">MemberExpression m</span>)</span> &#123;</span><br><span class="line">        Expression exp = <span class="keyword">this</span>.Visit(m.Expression);</span><br><span class="line">        <span class="keyword">if</span> (exp != m.Expression) &#123;</span><br><span class="line">            <span class="keyword">return</span> Expression.MakeMemberAccess(exp, m.Member);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> m;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">VisitMethodCall</span>(<span class="params">MethodCallExpression m</span>)</span> &#123;</span><br><span class="line">        Expression obj = <span class="keyword">this</span>.Visit(m.Object);</span><br><span class="line">        IEnumerable&lt;Expression&gt; <span class="keyword">args</span> = <span class="keyword">this</span>.VisitExpressionList(m.Arguments);</span><br><span class="line">        <span class="keyword">if</span> (obj != m.Object || <span class="keyword">args</span> != m.Arguments) &#123;</span><br><span class="line">            <span class="keyword">return</span> Expression.Call(obj, m.Method, <span class="keyword">args</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> m;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> ReadOnlyCollection&lt;Expression&gt; <span class="title">VisitExpressionList</span>(<span class="params">ReadOnlyCollection&lt;Expression&gt; original</span>)</span> &#123;</span><br><span class="line">        List&lt;Expression&gt; list = <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>, n = original.Count; i &lt; n; i++) &#123;</span><br><span class="line">            Expression p = <span class="keyword">this</span>.Visit(original[i]);</span><br><span class="line">            <span class="keyword">if</span> (list != <span class="literal">null</span>) &#123;</span><br><span class="line">                list.Add(p);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> (p != original[i]) &#123;</span><br><span class="line">                list = <span class="keyword">new</span> List&lt;Expression&gt;(n);</span><br><span class="line">                <span class="keyword">for</span> (<span class="built_in">int</span> j = <span class="number">0</span>; j &lt; i; j++) &#123;</span><br><span class="line">                    list.Add(original[j]);</span><br><span class="line">                &#125;</span><br><span class="line">                list.Add(p);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (list != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> list.AsReadOnly();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> original;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> MemberAssignment <span class="title">VisitMemberAssignment</span>(<span class="params">MemberAssignment assignment</span>)</span> &#123;</span><br><span class="line">        Expression e = <span class="keyword">this</span>.Visit(assignment.Expression);</span><br><span class="line">        <span class="keyword">if</span> (e != assignment.Expression) &#123;</span><br><span class="line">            <span class="keyword">return</span> Expression.Bind(assignment.Member, e);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> assignment;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> MemberMemberBinding <span class="title">VisitMemberMemberBinding</span>(<span class="params">MemberMemberBinding binding</span>)</span> &#123;</span><br><span class="line">        IEnumerable&lt;MemberBinding&gt; bindings = <span class="keyword">this</span>.VisitBindingList(binding.Bindings);</span><br><span class="line">        <span class="keyword">if</span> (bindings != binding.Bindings) &#123;</span><br><span class="line">            <span class="keyword">return</span> Expression.MemberBind(binding.Member, bindings);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> binding;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> MemberListBinding <span class="title">VisitMemberListBinding</span>(<span class="params">MemberListBinding binding</span>)</span> &#123;</span><br><span class="line">        IEnumerable&lt;ElementInit&gt; initializers = <span class="keyword">this</span>.VisitElementInitializerList(binding.Initializers);</span><br><span class="line">        <span class="keyword">if</span> (initializers != binding.Initializers) &#123;</span><br><span class="line">            <span class="keyword">return</span> Expression.ListBind(binding.Member, initializers);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> binding;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> IEnumerable&lt;MemberBinding&gt; <span class="title">VisitBindingList</span>(<span class="params">ReadOnlyCollection&lt;MemberBinding&gt; original</span>)</span> &#123;</span><br><span class="line">        List&lt;MemberBinding&gt; list = <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>, n = original.Count; i &lt; n; i++) &#123;</span><br><span class="line">            MemberBinding b = <span class="keyword">this</span>.VisitBinding(original[i]);</span><br><span class="line">            <span class="keyword">if</span> (list != <span class="literal">null</span>) &#123;</span><br><span class="line">                list.Add(b);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> (b != original[i]) &#123;</span><br><span class="line">                list = <span class="keyword">new</span> List&lt;MemberBinding&gt;(n);</span><br><span class="line">                <span class="keyword">for</span> (<span class="built_in">int</span> j = <span class="number">0</span>; j &lt; i; j++) &#123;</span><br><span class="line">                    list.Add(original[j]);</span><br><span class="line">                &#125;</span><br><span class="line">                list.Add(b);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (list != <span class="literal">null</span>)</span><br><span class="line">            <span class="keyword">return</span> list;</span><br><span class="line">        <span class="keyword">return</span> original;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> IEnumerable&lt;ElementInit&gt; <span class="title">VisitElementInitializerList</span>(<span class="params">ReadOnlyCollection&lt;ElementInit&gt; original</span>)</span> &#123;</span><br><span class="line">        List&lt;ElementInit&gt; list = <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>, n = original.Count; i &lt; n; i++) &#123;</span><br><span class="line">            ElementInit <span class="keyword">init</span> = <span class="keyword">this</span>.VisitElementInitializer(original[i]);</span><br><span class="line">            <span class="keyword">if</span> (list != <span class="literal">null</span>) &#123;</span><br><span class="line">                list.Add(<span class="keyword">init</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">init</span> != original[i]) &#123;</span><br><span class="line">                list = <span class="keyword">new</span> List&lt;ElementInit&gt;(n);</span><br><span class="line">                <span class="keyword">for</span> (<span class="built_in">int</span> j = <span class="number">0</span>; j &lt; i; j++) &#123;</span><br><span class="line">                    list.Add(original[j]);</span><br><span class="line">                &#125;</span><br><span class="line">                list.Add(<span class="keyword">init</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (list != <span class="literal">null</span>)</span><br><span class="line">            <span class="keyword">return</span> list;</span><br><span class="line">        <span class="keyword">return</span> original;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">VisitLambda</span>(<span class="params">LambdaExpression lambda</span>)</span> &#123;</span><br><span class="line">        Expression body = <span class="keyword">this</span>.Visit(lambda.Body);</span><br><span class="line">        <span class="keyword">if</span> (body != lambda.Body) &#123;</span><br><span class="line">            <span class="keyword">return</span> Expression.Lambda(lambda.Type, body, lambda.Parameters);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> lambda;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> NewExpression <span class="title">VisitNew</span>(<span class="params">NewExpression nex</span>)</span> &#123;</span><br><span class="line">        IEnumerable&lt;Expression&gt; <span class="keyword">args</span> = <span class="keyword">this</span>.VisitExpressionList(nex.Arguments);</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">args</span> != nex.Arguments) &#123;</span><br><span class="line">            <span class="keyword">if</span> (nex.Members != <span class="literal">null</span>)</span><br><span class="line">                <span class="keyword">return</span> Expression.New(nex.Constructor, <span class="keyword">args</span>, nex.Members);</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                <span class="keyword">return</span> Expression.New(nex.Constructor, <span class="keyword">args</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> nex;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">VisitMemberInit</span>(<span class="params">MemberInitExpression <span class="keyword">init</span></span>)</span> &#123;</span><br><span class="line">        NewExpression n = <span class="keyword">this</span>.VisitNew(<span class="keyword">init</span>.NewExpression);</span><br><span class="line">        IEnumerable&lt;MemberBinding&gt; bindings = <span class="keyword">this</span>.VisitBindingList(<span class="keyword">init</span>.Bindings);</span><br><span class="line">        <span class="keyword">if</span> (n != <span class="keyword">init</span>.NewExpression || bindings != <span class="keyword">init</span>.Bindings) &#123;</span><br><span class="line">            <span class="keyword">return</span> Expression.MemberInit(n, bindings);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">init</span>;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">VisitListInit</span>(<span class="params">ListInitExpression <span class="keyword">init</span></span>)</span> &#123;</span><br><span class="line">        NewExpression n = <span class="keyword">this</span>.VisitNew(<span class="keyword">init</span>.NewExpression);</span><br><span class="line">        IEnumerable&lt;ElementInit&gt; initializers = <span class="keyword">this</span>.VisitElementInitializerList(<span class="keyword">init</span>.Initializers);</span><br><span class="line">        <span class="keyword">if</span> (n != <span class="keyword">init</span>.NewExpression || initializers != <span class="keyword">init</span>.Initializers) &#123;</span><br><span class="line">            <span class="keyword">return</span> Expression.ListInit(n, initializers);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">init</span>;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">VisitNewArray</span>(<span class="params">NewArrayExpression na</span>)</span> &#123;</span><br><span class="line">        IEnumerable&lt;Expression&gt; exprs = <span class="keyword">this</span>.VisitExpressionList(na.Expressions);</span><br><span class="line">        <span class="keyword">if</span> (exprs != na.Expressions) &#123;</span><br><span class="line">            <span class="keyword">if</span> (na.NodeType == ExpressionType.NewArrayInit) &#123;</span><br><span class="line">                <span class="keyword">return</span> Expression.NewArrayInit(na.Type.GetElementType(), exprs);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> Expression.NewArrayBounds(na.Type.GetElementType(), exprs);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> na;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">virtual</span> Expression <span class="title">VisitInvocation</span>(<span class="params">InvocationExpression iv</span>)</span> &#123;</span><br><span class="line">        IEnumerable&lt;Expression&gt; <span class="keyword">args</span> = <span class="keyword">this</span>.VisitExpressionList(iv.Arguments);</span><br><span class="line">        Expression expr = <span class="keyword">this</span>.Visit(iv.Expression);</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">args</span> != iv.Arguments || expr != iv.Expression) &#123;</span><br><span class="line">            <span class="keyword">return</span> Expression.Invoke(expr, <span class="keyword">args</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> iv;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;英文原文是&lt;a href=&quot;https://social.msdn.microsoft.com/profile/matt%20warren%20-%20msft/&quot; title=&quot;Matt Warren&quot;&gt;Matt Warren&lt;/a&gt;发表在MSDN Blogs的系列文章之一，英文渣渣，翻译&lt;strong&gt;不供参考&lt;/strong&gt;，请直接&lt;a href=&quot;http://blogs.msdn.com/b/mattwar/archive/2007/07/31/linq-building-an-iqueryable-provider-part-ii.aspx&quot;&gt;看原文&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在上篇文章中，我们已经打好了基础，定义了可重用的&lt;code&gt;IQueryable&lt;/code&gt;和&lt;code&gt;IQueryProvider&lt;/code&gt;，它们分别是&lt;code&gt;Query&amp;lt;T&amp;gt;&lt;/code&gt;类和&lt;code&gt;QueryProvider&lt;/code&gt;类，现在我们来构建一个真正有用的提供程序。我之前说过，一个查询提供程序所做的事就是执行一些“代码”，这些“代码”使用表达式树而不是真正的IL语言来定义。当然，这并不一定是传统意义上的执行。比如说，LINQ to SQL就是将查询表达式翻译为SQL然后送到服务器中去执行的。&lt;/p&gt;
&lt;p&gt;我下面给出的示例与LINQ to SQL有点类似，都是针对一个DAO provider对查询进行翻译和执行。但是，我要做个免责声明，在任何意义上，我给出的示例都不是一个完整的提供程序。我只会翻译&lt;code&gt;Where&lt;/code&gt;操作，并且只支持在谓词中使用一个字段引用和一些简单的运算符，除此之外没有任何复杂的东西。以后我可能会扩展这个提供程序，但现在仅用于说明的目的。所以不要以为复制粘贴就能得到高质量的代码。&lt;/p&gt;
&lt;p&gt;这个提供程序主要做两件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将查询翻译为SQL命令&lt;/li&gt;
&lt;li&gt;将执行命令得到的结果转换为对象&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    
    <category term="C♯" scheme="https://www.liuwj.me/tags/C-Sharp/"/>
    
  </entry>
  
  <entry>
    <title>「译」LINQ: Building an IQueryable Provider - Part I: Reusable IQueryable base classes</title>
    <link href="https://www.liuwj.me/posts/linq-building-an-iqueryable-provider-part-i/"/>
    <id>https://www.liuwj.me/posts/linq-building-an-iqueryable-provider-part-i/</id>
    <published>2016-01-13T00:00:00.000Z</published>
    <updated>2026-03-01T02:56:55.949Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>英文原文是<a href="https://social.msdn.microsoft.com/profile/matt%20warren%20-%20msft/" title="Matt Warren">Matt Warren</a>发表在MSDN Blogs的系列文章之一，英文渣渣，翻译<strong>不供参考</strong>，请直接<a href="http://blogs.msdn.com/b/mattwar/archive/2007/07/30/linq-building-an-iqueryable-provider-part-i.aspx">看原文</a>。</p></blockquote><p>这段时间我一直打算写一个系列的文章来介绍如何使用<code>IQueryable</code>构建LINQ提供程序。也一直有人通过微软内部邮件、论坛提问或者直接给我发邮件的方式来给我这方面的建议。当然，通常我都会回复“我正在做一个详尽的Sample来给你们展示这一切”，告诉他们很快所有内容都会发布。但是，相比仅仅发布一个完整的Sample，我觉得一步一步循序渐进地阐述才是一个明智的选择，这样我才能深挖里面的所有细节，而不是仅仅把东西扔给你们，让你们自生自灭。</p><p>我要说的第一件事是，在Beta 2版本里面，<code>IQueryable</code>不再只是一个接口，它被分成了两个：<code>IQueryable</code>和<code>IQueryProvider</code>。在实现这两个接口之前，我们先过一遍它们的内容。<br />使用Visual Studio的“go to definition”功能，你可以看到下面的代码</p><span id="more"></span><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title">IQueryable</span> : <span class="title">IEnumerable</span> &#123;       </span><br><span class="line">    Type ElementType &#123; <span class="keyword">get</span>; &#125;</span><br><span class="line">    Expression Expression &#123; <span class="keyword">get</span>; &#125;</span><br><span class="line">    IQueryProvider Provider &#123; <span class="keyword">get</span>; &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title">IQueryable</span>&lt;<span class="title">T</span>&gt; : <span class="title">IEnumerable</span>&lt;<span class="title">T</span>&gt;, <span class="title">IQueryable</span>, <span class="title">IEnumerable</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当然，<code>IQueryable</code>现在已经没什么好看的，有趣的内容都被放到了新接口<code>IQueryProvider</code>那里。如你所见，<code>IQueryable</code>只有三个只读的属性。第一个属性返回元素的类型（或者<code>IQueryable&lt;T&gt;</code>里面的<code>T</code>）。注意，所有实现<code>IQueryable</code>的类都必须同时实现<code>IQueryable&lt;T&gt;</code>，反之亦然。泛型的<code>IQueryable&lt;T&gt;</code>是在方法签名里面使用得最频繁的。非泛型的<code>IQueryable</code>的存在主要是为了提供一个弱类型的入口，该入口主要应用在动态构建query的场景之中。</p><p>第二个属性返回这个<code>IQueryable</code>对象对应的<code>Expression</code>，这正是<code>IQueryable</code>的精髓所在。在<code>IQueryable</code>封装之下的真正的“查询”是一个表达式树，它将query对象表示为一个由LINQ查询方法/操作符组成的树形结构，这是构建一个LINQ提供程序必须理解的原理。仔细看你就会发现，整个<code>IQueryable</code>的结构体系（包括LING标准查询操作符的<code>System.Linq.Queryable</code>版本）只是自动为你创建了表达式树。当你使用<code>Queryable.Where</code>方法来过滤<code>IQueryable</code>中的数据的时候，它只是简单地创建了一个新的<code>IQueryable</code>对象，并在原有的表达式树顶上创建一个<code>MethodCallExpression</code>类型的节点，该节点表示一次<code>Queryable.Where</code>方法的调用。不信？你自己试试看就知道了。</p><p>现在就只剩最后一个属性，这个属性返回新接口<code>IQueryProvider</code>的实例。我们把所有构造<code>IQueryable</code>实例和执行查询的方法都分离到了这个新接口中，这样能更加清晰地表示出查询提供程序的概念。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title">IQueryProvider</span> &#123;</span><br><span class="line">    <span class="function">IQueryable <span class="title">CreateQuery</span>(<span class="params">Expression expression</span>)</span>;</span><br><span class="line">    <span class="function"><span class="title">IQueryable</span>&lt;<span class="title">TElement</span>&gt; <span class="title">CreateQuery</span>&lt;<span class="title">TElement</span>&gt;(<span class="params">Expression expression</span>)</span>;</span><br><span class="line">    <span class="function"><span class="built_in">object</span> <span class="title">Execute</span>(<span class="params">Expression expression</span>)</span>;</span><br><span class="line">    <span class="function">TResult <span class="title">Execute</span>&lt;<span class="title">TResult</span>&gt;(<span class="params">Expression expression</span>)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>看到这个<code>IQueryProvider</code>接口，你可能会疑惑为什么有这么多方法。实际上这里只有两个操作，<code>CreateQuery</code>和<code>Execute</code>，只不过每个操作都有一个泛型的版本和一个非泛型的版本。当你直接在代码里面写查询的时候，一般都是调用泛型的版本。使用泛型的版本可以避免使用反射创建实例，因此性能更佳。</p><p>正如其名，<code>CreateQuery</code>方法的作用是根据指定的表达式树创建一个新的<code>IQueryable</code>对象。当这个方法被调用时，你的提供程序应该返回一个<code>IQueryable</code>对象，这个对象被枚举的时候会调用你的提供程序来处理这个指定的表达式。<code>Queryable</code>的标准查询操作符就是调用这个方法来创建与你的提供程序保持关联的<code>IQueryable</code>对象。注意，调用者可能会传给你的这个API一个任意的表达式树，对你的提供程序而言，传入的表达式树甚至可能是非法的，但是可以保证的是它一定会符合<code>IQueryable</code>对象的类型要求。<code>IQueryable</code>对象包含了一个表达式，这个表达式是一个代码的片段，当它转换为真正的代码并且执行的时候就会重新构造一个等价的<code>IQueryable</code>对象。</p><p><code>Execute</code>方法是你的提供程序真正执行查询表达式的入口。应提供一个明确的<code>Execute</code>方法而不要仅仅依赖于<code>IEnumerable.GetEnumerator()</code>，以支持那些不必返回一个序列的查询。比如，这个查询“<code>myquery.Count()</code>”返回一个整数，该查询的表达式树是对返回整数的<code>Count</code>方法的调用。<code>Queryable.Count</code>方法（以及其他类似的聚合方法）就是调用<code>Execute</code>来“立即”执行查询。</p><p>讲到这里，是不是看起来就没那么难了？你自己也可以很轻松地实现所有的方法对吧？但是何必这么麻烦呢，我在下面就会给出代码。当然<code>Execute</code>方法除外，这个我会在以后的文章中给出。</p><p>让我们先从<code>IQueryable</code>开始。因为这个接口已经被划分成了两个，所以现在可以只用实现一次<code>IQueryable</code>，然后把它用在任意一个<code>IQueryProvider</code>中。下面给出一个<code>Query&lt;T&gt;</code>类，它实现了<code>IQueryable&lt;T&gt;</code>以及其他一系列的接口。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title">Query</span>&lt;<span class="title">T</span>&gt; : <span class="title">IQueryable</span>&lt;<span class="title">T</span>&gt;, <span class="title">IQueryable</span>, <span class="title">IEnumerable</span>&lt;<span class="title">T</span>&gt;, <span class="title">IEnumerable</span>, <span class="title">IOrderedQueryable</span>&lt;<span class="title">T</span>&gt;, <span class="title">IOrderedQueryable</span> &#123;</span><br><span class="line">    QueryProvider provider;</span><br><span class="line">    Expression expression;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Query</span>(<span class="params">QueryProvider provider</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (provider == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> ArgumentNullException(<span class="string">&quot;provider&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">this</span>.provider = provider;</span><br><span class="line">        <span class="keyword">this</span>.expression = Expression.Constant(<span class="keyword">this</span>);</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Query</span>(<span class="params">QueryProvider provider, Expression expression</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (provider == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> ArgumentNullException(<span class="string">&quot;provider&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (expression == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> ArgumentNullException(<span class="string">&quot;expression&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (!<span class="keyword">typeof</span>(IQueryable&lt;T&gt;).IsAssignableFrom(expression.Type)) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> ArgumentOutOfRangeException(<span class="string">&quot;expression&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">this</span>.provider = provider;</span><br><span class="line">        <span class="keyword">this</span>.expression = expression;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    Expression IQueryable.Expression &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.expression; &#125;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    Type IQueryable.ElementType &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">typeof</span>(T); &#125;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    IQueryProvider IQueryable.Provider &#123;</span><br><span class="line">        <span class="keyword">get</span> &#123; <span class="keyword">return</span> <span class="keyword">this</span>.provider; &#125;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> IEnumerator&lt;T&gt; <span class="title">GetEnumerator</span>()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> ((IEnumerable&lt;T&gt;)<span class="keyword">this</span>.provider.Execute(<span class="keyword">this</span>.expression)).GetEnumerator();</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    IEnumerator IEnumerable.GetEnumerator() &#123;</span><br><span class="line">        <span class="keyword">return</span> ((IEnumerable)<span class="keyword">this</span>.provider.Execute(<span class="keyword">this</span>.expression)).GetEnumerator();</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">override</span> <span class="built_in">string</span> <span class="title">ToString</span>()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.provider.GetQueryText(<span class="keyword">this</span>.expression);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>你看，<code>IQueryable</code>的实现十分简单。这个小对象所做的事情仅仅是保持一颗表达式树和一个查询提供者的实例，而查询提供者才是真正有趣的地方。</p><p>好了，下面把<code>Query&lt;T&gt;</code>类中引用到的<code>QueryProvider</code>给出，它是一个抽象类。一个真正的提供程序只需继承这个类，实现里面的<code>Execute</code>抽象方法。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title">QueryProvider</span> : <span class="title">IQueryProvider</span> &#123;</span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="title">QueryProvider</span>()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    IQueryable&lt;S&gt; IQueryProvider.CreateQuery&lt;S&gt;(Expression expression) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> Query&lt;S&gt;(<span class="keyword">this</span>, expression);</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    IQueryable IQueryProvider.CreateQuery(Expression expression) &#123;</span><br><span class="line">        Type elementType = TypeSystem.GetElementType(expression.Type);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> (IQueryable)Activator.CreateInstance(<span class="keyword">typeof</span>(Query&lt;&gt;).MakeGenericType(elementType), <span class="keyword">new</span> <span class="built_in">object</span>[] &#123; <span class="keyword">this</span>, expression &#125;);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">catch</span> (TargetInvocationException tie) &#123;</span><br><span class="line">            <span class="keyword">throw</span> tie.InnerException;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    S IQueryProvider.Execute&lt;S&gt;(Expression expression) &#123;</span><br><span class="line">        <span class="keyword">return</span> (S)<span class="keyword">this</span>.Execute(expression);</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="built_in">object</span> IQueryProvider.Execute(Expression expression) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.Execute(expression);</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="built_in">string</span> <span class="title">GetQueryText</span>(<span class="params">Expression expression</span>)</span>;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="built_in">object</span> <span class="title">Execute</span>(<span class="params">Expression expression</span>)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个抽象类实现了<code>IQueryProvider</code>接口。两个<code>CreateQuery</code>方法负责创建<code>Query&lt;T&gt;</code>的实例，两个<code>Execute</code>方法将执行操作交给了尚未实现的<code>Execute</code>抽象方法。</p><p>我认为你可以把这个当成构建LINQ <code>IQueryable</code>提供程序的样板代码。真正的执行操作放在<code>Execute</code>方法中，在这里，你的提供程序可以通过检查表达式树来理解查询的具体含义，而这就是我接下来要讲的内容。</p><p>更新：<br />我好像忘了定义在代码里面用到的helper类，下面给出。</p><figure class="highlight cs"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title">TypeSystem</span> &#123;</span><br><span class="line">    <span class="function"><span class="keyword">internal</span> <span class="keyword">static</span> Type <span class="title">GetElementType</span>(<span class="params">Type seqType</span>)</span> &#123;</span><br><span class="line">        Type ienum = FindIEnumerable(seqType);</span><br><span class="line">        <span class="keyword">if</span> (ienum == <span class="literal">null</span>) <span class="keyword">return</span> seqType;</span><br><span class="line">        <span class="keyword">return</span> ienum.GetGenericArguments()[<span class="number">0</span>];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> Type <span class="title">FindIEnumerable</span>(<span class="params">Type seqType</span>)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (seqType == <span class="literal">null</span> || seqType == <span class="keyword">typeof</span>(<span class="built_in">string</span>))</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">if</span> (seqType.IsArray)</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">typeof</span>(IEnumerable&lt;&gt;).MakeGenericType(seqType.GetElementType());</span><br><span class="line">        <span class="keyword">if</span> (seqType.IsGenericType) &#123;</span><br><span class="line">            <span class="keyword">foreach</span> (Type arg <span class="keyword">in</span> seqType.GetGenericArguments()) &#123;</span><br><span class="line">                Type ienum = <span class="keyword">typeof</span>(IEnumerable&lt;&gt;).MakeGenericType(arg);</span><br><span class="line">                <span class="keyword">if</span> (ienum.IsAssignableFrom(seqType)) &#123;</span><br><span class="line">                    <span class="keyword">return</span> ienum;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        Type[] ifaces = seqType.GetInterfaces();</span><br><span class="line">        <span class="keyword">if</span> (ifaces != <span class="literal">null</span> &amp;&amp; ifaces.Length &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">foreach</span> (Type iface <span class="keyword">in</span> ifaces) &#123;</span><br><span class="line">                Type ienum = FindIEnumerable(iface);</span><br><span class="line">                <span class="keyword">if</span> (ienum != <span class="literal">null</span>) <span class="keyword">return</span> ienum;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (seqType.BaseType != <span class="literal">null</span> &amp;&amp; seqType.BaseType != <span class="keyword">typeof</span>(<span class="built_in">object</span>)) &#123;</span><br><span class="line">            <span class="keyword">return</span> FindIEnumerable(seqType.BaseType);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>好吧，我知道这个helper类的代码比其他地方的都多。<br />Sigh. <i class="emoji emoji-smile"></i></p>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;英文原文是&lt;a href=&quot;https://social.msdn.microsoft.com/profile/matt%20warren%20-%20msft/&quot; title=&quot;Matt Warren&quot;&gt;Matt Warren&lt;/a&gt;发表在MSDN Blogs的系列文章之一，英文渣渣，翻译&lt;strong&gt;不供参考&lt;/strong&gt;，请直接&lt;a href=&quot;http://blogs.msdn.com/b/mattwar/archive/2007/07/30/linq-building-an-iqueryable-provider-part-i.aspx&quot;&gt;看原文&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这段时间我一直打算写一个系列的文章来介绍如何使用&lt;code&gt;IQueryable&lt;/code&gt;构建LINQ提供程序。也一直有人通过微软内部邮件、论坛提问或者直接给我发邮件的方式来给我这方面的建议。当然，通常我都会回复“我正在做一个详尽的Sample来给你们展示这一切”，告诉他们很快所有内容都会发布。但是，相比仅仅发布一个完整的Sample，我觉得一步一步循序渐进地阐述才是一个明智的选择，这样我才能深挖里面的所有细节，而不是仅仅把东西扔给你们，让你们自生自灭。&lt;/p&gt;
&lt;p&gt;我要说的第一件事是，在Beta 2版本里面，&lt;code&gt;IQueryable&lt;/code&gt;不再只是一个接口，它被分成了两个：&lt;code&gt;IQueryable&lt;/code&gt;和&lt;code&gt;IQueryProvider&lt;/code&gt;。在实现这两个接口之前，我们先过一遍它们的内容。&lt;br /&gt;
使用Visual Studio的“go to definition”功能，你可以看到下面的代码&lt;/p&gt;</summary>
    
    
    
    
    <category term="C♯" scheme="https://www.liuwj.me/tags/C-Sharp/"/>
    
  </entry>
  
</feed>
