鲈鱼得水 發表於 2025-12-1 11:21:00

【EF Core】三种方法记录生成的 SQL 语句

<p>原本计划 N 天前写的内容,无奈拖到今天。大伙伴们可能都了解,年近岁末,风干物燥,bug 特多,改需求的精力特旺盛。有几个工厂的项目需要不同程度的修改或修复。这些项目都是老周个人名义与他们长期合作的(有些项目已断尾了,他们觉得不用再改了),所以不一定都是新项目,有两三个都维护好几年了。</p>
<p>今天咱们的主题是记录 SQL 语句。用过 EF 的都知道,它可以将 LINQ 表达式树翻译成 SQL 语句,然后发送到数据库执行。这个框架从 Framework 时代走到 Core 时代,虽说不是什么新鲜技术,但这活真的是好活,以面向对象的方式与数据库交互是真的爽。</p>
<p>将 LINQ 转译为 SQL 是框架内部功能,官方团队或许也没考虑让我们做太多的扩展(实际开发中也的确很少),因此,框架并没有提供独立的服务让我们去做表达式树的翻译。在执行查询时,EF Core 是经过几个步骤的,这个可以看看&nbsp;QueryCompilationContext 类的源代码。其实处理查询转译的代码是写在这个类里面的,不是 Database 类。上次在某公司有位妹子程序员问过老周,她想看看 LINQ 翻译 SQL 的大致过程,可在源代码中找不到。你不要惊讶,这个公司的团队绝对少见,七个成员,四个是女的,恐怕你都找不出第二个这样的团队。</p>
<p>老周告诉她,源代码庞大,直接拿着看很多东西不好找的,你可以用调试进入源码,一步步跟进去,才比较好找。不废话了,咱们看代码。</p>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span> Expression&lt;Func&lt;QueryContext, TResult&gt;&gt; CreateQueryExecutorExpression&lt;TResult&gt;<span style="color: rgba(0, 0, 0, 1)">(Expression query)
    {
      </span><span style="color: rgba(0, 0, 255, 1)">var</span> queryAndEventData =<span style="color: rgba(0, 0, 0, 1)"><strong><span style="background-color: rgba(255, 255, 0, 1)"> Logger.QueryCompilationStarting(Dependencies.Context, _expressionPrinter, query)</span></strong>;
      </span><span style="color: rgba(0, 0, 255, 1)">var</span> interceptedQuery =<span style="color: rgba(0, 0, 0, 1)"> queryAndEventData.Query;

      </span><span style="color: rgba(0, 0, 255, 1)">var</span> preprocessedQuery = <strong><span style="background-color: rgba(255, 255, 0, 1)">_queryTranslationPreprocessorFactory.Create(<span style="color: rgba(0, 0, 255, 1)">this</span></span></strong><span style="color: rgba(0, 0, 0, 1)"><strong><span style="background-color: rgba(255, 255, 0, 1)">).Process(interceptedQuery)</span></strong>;
      </span><span style="color: rgba(0, 0, 255, 1)">var</span> translatedQuery = <strong><span style="background-color: rgba(255, 255, 0, 1)">_queryableMethodTranslatingExpressionVisitorFactory.Create(<span style="color: rgba(0, 0, 255, 1)">this</span></span></strong><span style="color: rgba(0, 0, 0, 1)"><strong><span style="background-color: rgba(255, 255, 0, 1)">).Translate(preprocessedQuery)</span></strong>;
      </span><span style="color: rgba(0, 0, 255, 1)">var</span> postprocessedQuery = <span style="background-color: rgba(255, 255, 0, 1)"><strong>_queryTranslationPostprocessorFactory.Create(<span style="color: rgba(0, 0, 255, 1)">this</span></strong></span><span style="color: rgba(0, 0, 0, 1)"><span style="background-color: rgba(255, 255, 0, 1)"><strong>).Process(translatedQuery)</strong></span>;

      </span><span style="color: rgba(0, 0, 255, 1)">var</span> compiledQuery = <strong><span style="background-color: rgba(255, 255, 0, 1)">_shapedQueryCompilingExpressionVisitorFactory.Create(<span style="color: rgba(0, 0, 255, 1)">this</span></span></strong><span style="color: rgba(0, 0, 0, 1)"><strong><span style="background-color: rgba(255, 255, 0, 1)">).Visit(postprocessedQuery)</span></strong>;

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> If any additional parameters were added during the compilation phase (e.g. entity equality ID expression),
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> wrap the query with code adding those parameters to the query context</span>
      <span style="color: rgba(0, 0, 255, 1)">var</span> compiledQueryWithRuntimeParameters =<span style="color: rgba(0, 0, 0, 1)"> InsertRuntimeParameters(compiledQuery);

      </span><span style="color: rgba(0, 0, 255, 1)">return</span> Expression.Lambda&lt;Func&lt;QueryContext, TResult&gt;&gt;<span style="color: rgba(0, 0, 0, 1)">(
            compiledQueryWithRuntimeParameters,
            QueryContextParameter);
    }</span></pre>
</div>
<p>这代码一旦展开是非常复杂的,你不仅要有使用 LINQ 表达式树的知识,还得看懂其思路,所以,没兴趣的话就不用看了。而且看不懂也不影响写代码。大体过程是这样的:</p>
<p>1、先执行拦截器。拦截器这东西老周以后会介绍,拦截器可以拦截你执行的 LINQ 表达式树,并且你可以在翻译之前修改它。</p>
<p>2、预处理。这里面又是一堆处理,如参数命名规整化、把如 long.Max 这样的方法调用标准化为 Math.Max 调用、表达式简化等等。</p>
<p>3、特殊方法调用转换,如调用 Where、All、FirstOrDefault 这样标准查询方法,还有 ExecuteUpdate、ExecuteDelete 这些专用方法的调用转换等。</p>
<p>4、转换扫尾工作,这个主要是不同数据库的特殊处理,比如,Sqlite 和 SQL Server 的处理不同。</p>
<p>5、正式转译为 SQL 语句。</p>
<p>6、生成 Lambda 表达式树。这个委托接收&nbsp;QueryContext 类型的参数(可以用 IQueryContextFactory 服务创建),返回的结果一般是&nbsp;IEnumerable&lt;T&gt;。</p>
<p>想想,调用这些代码获取 SQL 太麻烦,这等同于把人家源代码抄一遍了。其实,单纯的把 LINQ 转 SQL 意义不大的,许多场景下,可能最需要的是日志功能——记录发送到数据库的 SQL 语句。</p>
<p>&nbsp;</p>
<p>好了,上面的只是理论铺设,接下来咱们聊主题。咱们有两种方法可以记录SQL语句,不废话,老周直接说答案:</p>
<p><span style="color: rgba(0, 0, 128, 1)">1、通过日志 + 事件过滤功能。这个最简单;</span></p>
<p><span style="color: rgba(0, 0, 128, 1)">2、通过拦截器拦截 DbCommand 对象,从而获取 SQL 语句;</span></p>
<p><span style="color: rgba(0, 0, 128, 1)">3、ToQueryString 扩展方法。</span></p>
<p><span style="background-color: rgba(255, 255, 255, 1); font-size: 15px; color: rgba(255, 0, 0, 1)"><strong>修改说明:本文当初写的是两种方法,因为老周老糊涂了,遗忘了还有 ToQueryString 扩展方法,现改为三种方法。</strong></span></p>
<p>&nbsp;</p>
<p>-----------------------------------------------------------------------------------------------------------------------------------</p>
<p>先说第一种,先写个实体类,随便写就行。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Song
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> ID { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> Name { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span>; } = <span style="color: rgba(0, 0, 255, 1)">null</span>!<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span>? Artist { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">long</span> Duration { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
}</span></pre>
</div>
<p>然后写数据库上下文。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> MyDbContext : DbContext
{
    </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
      optionsBuilder.UseSqlite(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">data source=demo.db</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
    }

    </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> OnModelCreating(ModelBuilder modelBuilder)
    {
      modelBuilder.Entity</span>&lt;Song&gt;(et =&gt;<span style="color: rgba(0, 0, 0, 1)">
      {
            et.ToTable(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">tb_songs</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
            et.HasKey(x </span>=&gt; x.ID).HasName(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">PK_Song</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
            et.Property(a </span>=&gt; a.Name).HasMaxLength(<span style="color: rgba(128, 0, 128, 1)">20</span><span style="color: rgba(0, 0, 0, 1)">);
      });
    }

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 公开数据集合</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> DbSet&lt;Song&gt; Songs { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
}</span></pre>
</div>
<p>写好后回过头看&nbsp;OnConfiguring 方法,现在咱们要配置日志。</p>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
      optionsBuilder.UseSqlite(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">data source=demo.db</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
      optionsBuilder.LogTo(
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 第一个委托:过滤事件</span>
            (eventid, loglevel) =&gt;<span style="color: rgba(0, 0, 0, 1)">
            {
                </span><span style="color: rgba(0, 0, 255, 1)">if</span>(<strong><span style="background-color: rgba(255, 255, 0, 1)">eventid.Id ==</span></strong><span style="color: rgba(0, 0, 0, 1)"><strong><span style="background-color: rgba(255, 255, 0, 1)"> RelationalEventId.CommandExecuting</span></strong>)
                {
                  </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
                }
                </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
            },
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 第二个委托:记录SQL</span>
            eventData =&gt;<span style="color: rgba(0, 0, 0, 1)">
            {
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 转换事件数据</span>
                <span style="color: rgba(0, 0, 255, 1)">if</span>(eventData <span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> CommandEventData data)
                {
                  </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 记录SQL</span>
                  Console.Write(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">命令源:{0}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, data.CommandSource);
                  Console.Write(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">,SQL 语句:{0}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, data.LogCommandText);
                  Console.Write(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\n\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
                }
            });
    }</span></pre>
</div>
<p>这里,LogTo 调用的是以下重载:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">virtual</span><span style="color: rgba(0, 0, 0, 1)"> DbContextOptionsBuilder LogTo(
    Func</span>&lt;EventId, LogLevel, <span style="color: rgba(0, 0, 255, 1)">bool</span>&gt;<span style="color: rgba(0, 0, 0, 1)"> filter,
    Action</span>&lt;EventData&gt; logger)</pre>
</div>
<p>filter 是个过滤器,EventId 表示相关事件,LogLevel 表示日志级别,如&nbsp;Information、Warning、Error 等。第三个是返回值,布尔类型。所以,这个委托的用法很明显,如果返回 false,表示不记录该事件的日志,第二个委托logger就不会调用;如果过滤器返回 true,表明要接收此事件的日志,此时,logger 委托会调用。</p>
<p>咱们的代码只关心&nbsp;CommandExecuting 事件,这是 DbCommand 执行之前触发的,如果是命令执行之后,会触发&nbsp;CommandExecuted 事件。咱们的目标明确——获取生成的 SQL 语句,其实这里也可以用&nbsp;CommandInitialized 事件。其实&nbsp;CommandInitialized、CommandExecuting、CommandExecuted 三个事件都能得到 SQL 语句,任意抓取一个用即可。</p>
<p>在第二个委托中,它有一个输入参数—— EventData,它是所有事件数据的基类,所以,在委托内部需要进行类型分析。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">if</span>(eventData <span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> CommandEventData data)
   ……</span></pre>
</div>
<p>不过这里我们不必关心其他类型,毕竟 filter 只选出一个事件,其他事件都返回 false,不会调用 logger 委托。</p>
<p>从&nbsp;LogCommandText 属性上就能得到 SQL 语句。另外,CommandSource 是一个枚举,它表示这个 SQL 语句是由哪个操作引发的。如</p>
<ul>
<li>SaveChanges:你调用 DbContext.SaveChanges 方法后保存数据时触发的。</li>
<li>Migrations:迁移数据库时触发,包括在运行阶段执行迁移,或者调用 Database.EnsureCreate 或 EnsureDelete 等方法也会触发。</li>
<li>LinqQuery:这个熟悉,就是你常规操作,使用 LINQ 查询转 SQL 后执行。</li>
<li>ExecuteDelete 与&nbsp;ExecuteUpdate:就是调用 ExecuteUpdate、ExecuteDelete 方法时触发。</li>
</ul>
<p>好,咱们试一下,先用 EnsureCreate 创建数据库,然后执行一个查询。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span> <span style="color: rgba(0, 0, 255, 1)">var</span> ctx = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MyDbContext();
ctx.Database.EnsureCreated();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 查询</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> res =<span style="color: rgba(0, 0, 0, 1)"> ctx.Songs
                  .Where(s </span>=&gt; s.ID &gt; <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">)
                  .ToArray();</span></pre>
</div>
<p>运行一下看看。结果如下:</p>
<div class="cnblogs_code">
<pre>命令源:Migrations,SQL 语句:PRAGMA journal_mode <span style="color: rgba(128, 128, 128, 1)">=</span> <span style="color: rgba(255, 0, 0, 1)">'</span><span style="color: rgba(255, 0, 0, 1)">wal</span><span style="color: rgba(255, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;

命令源:Migrations,SQL 语句:</span><span style="color: rgba(0, 0, 255, 1)">CREATE</span> <span style="color: rgba(0, 0, 255, 1)">TABLE</span><span style="color: rgba(0, 0, 0, 1)"> "tb_songs" (
    "ID" </span><span style="color: rgba(0, 0, 255, 1)">INTEGER</span> <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span> <span style="color: rgba(0, 0, 255, 1)">CONSTRAINT</span> "PK_Song" <span style="color: rgba(0, 0, 255, 1)">PRIMARY</span> <span style="color: rgba(0, 0, 255, 1)">KEY</span><span style="color: rgba(0, 0, 0, 1)"> AUTOINCREMENT,
    "Name" </span><span style="color: rgba(0, 0, 255, 1)">TEXT</span> <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
    "Artist" </span><span style="color: rgba(0, 0, 255, 1)">TEXT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">,
    "Duration" </span><span style="color: rgba(0, 0, 255, 1)">INTEGER</span> <span style="color: rgba(128, 128, 128, 1)">NOT</span> <span style="color: rgba(0, 0, 255, 1)">NULL</span><span style="color: rgba(0, 0, 0, 1)">
);


命令源:LinqQuery,SQL 语句:</span><span style="color: rgba(0, 0, 255, 1)">SELECT</span><span style="color: rgba(0, 0, 0, 1)"> "t"."ID", "t"."Artist", "t"."Duration", "t"."Name"
</span><span style="color: rgba(0, 0, 255, 1)">FROM</span> "tb_songs" <span style="color: rgba(0, 0, 255, 1)">AS</span><span style="color: rgba(0, 0, 0, 1)"> "t"
</span><span style="color: rgba(0, 0, 255, 1)">WHERE</span> "t"."ID" <span style="color: rgba(128, 128, 128, 1)">&gt;</span> <span style="color: rgba(128, 0, 0, 1); font-weight: bold">2</span></pre>
</div>
<p>前两个语句的命令源都是 Migrations,这是创建数据库和表时的语句(SQLite 不需要 CREATE DATABASE 语句,直接建表)。第三个就是咱们执行查询生成的 SQL 语句,可以看到命令源是 LinqQuery。</p>
<p>&nbsp;</p>
<p>----------------------------------------------------------------------------------------------------------------------------------------------------------------</p>
<p>现在看一下第二种方案,咱们先把数据库上下文的&nbsp;OnConfiguring 方法中的日志配置注释掉。</p>
<p>现在,咱们实现自己的命令拦截器。</p>
<p>拦截器的基础接口是&nbsp;IInterceptor,它是个空接口,没有任何成员,仅作为标志。咱们一般不会直接实现它。</p>
<p>拦截命令,框架提供的是&nbsp;IDbCommandInterceptor 接口,它要求你实现以下成员:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">interface</span><span style="color: rgba(0, 0, 0, 1)"> IDbCommandInterceptor : IInterceptor
{
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 当 DbCommand 对象(不同数据库有具体的类)被创建前触发
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这个时候是获取不到 SQL 语句的,命令对象原则上还没创建
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 但是,你可以自己创建一个,并用 InterceptorResult 返回
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 你要么原样返回,要么用 SuppressWithResult 静态方法自己创建一个命令对象
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这时候 EF Core 会用你创建的命令对象代替内部代码所创建的命令对象
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 注意这里只是抑制内部创建命令对象而已,并不能阻止命令的执行</span>
    InterceptionResult&lt;DbCommand&gt; CommandCreating(CommandCorrelatedEventData eventData, InterceptionResult&lt;DbCommand&gt;<span style="color: rgba(0, 0, 0, 1)"> result)
    {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> result;
    }

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 命令对象创建后,这里是 EF Core 负责创建命令对象,你负责修改
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 不修改就原样返回。此时,你不能自己 new 命令对象了,只能修改属性</span>
<span style="color: rgba(0, 0, 0, 1)">    DbCommand CommandCreated(CommandEndEventData eventData, DbCommand result)
    {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> result;
    }

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这里可以获取到 SQL 了</span>
<span style="color: rgba(0, 0, 0, 1)">    DbCommand CommandInitialized(CommandEndEventData eventData, DbCommand result)
    </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> result;

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 和前面的命令对象一样,这里你可以用自己创建的 DataReader 代替框架内部创建的
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这是有查询结果的 reader,比如 SELECT 语句
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 此时还没有执行 SQL</span>
    InterceptionResult&lt;DbDataReader&gt; ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult&lt;DbDataReader&gt;<span style="color: rgba(0, 0, 0, 1)"> result)
      </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> result;

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 查询单个标量值之前调用此方法,你可以自己分配一个值来代替   
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 此时还没执行 SQL 语句</span>
    InterceptionResult&lt;<span style="color: rgba(0, 0, 255, 1)">object</span>&gt; ScalarExecuting(DbCommand command, CommandEventData eventData, InterceptionResult&lt;<span style="color: rgba(0, 0, 255, 1)">object</span>&gt;<span style="color: rgba(0, 0, 0, 1)"> result)
      </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> result;

   </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 执行无结果查询前触发,你可以自己弄一个结果值覆盖真实查询的结果
   </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 此时还没有执行 SQL 语句</span>
    InterceptionResult&lt;<span style="color: rgba(0, 0, 255, 1)">int</span>&gt; NonQueryExecuting(DbCommand command, CommandEventData eventData, InterceptionResult&lt;<span style="color: rgba(0, 0, 255, 1)">int</span>&gt;<span style="color: rgba(0, 0, 0, 1)"> result)
      </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> result;

   </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 异步版本</span>
    ValueTask&lt;InterceptionResult&lt;DbDataReader&gt;&gt;<span style="color: rgba(0, 0, 0, 1)"> ReaderExecutingAsync(
      DbCommand command,
      CommandEventData eventData,
      InterceptionResult</span>&lt;DbDataReader&gt;<span style="color: rgba(0, 0, 0, 1)"> result,
      CancellationToken cancellationToken </span>= <span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">)
      </span>=&gt; <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)">(result);

   </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 异步版本</span>
    ValueTask&lt;InterceptionResult&lt;<span style="color: rgba(0, 0, 255, 1)">object</span>&gt;&gt;<span style="color: rgba(0, 0, 0, 1)"> ScalarExecutingAsync(
      DbCommand command,
      CommandEventData eventData,
      InterceptionResult</span>&lt;<span style="color: rgba(0, 0, 255, 1)">object</span>&gt;<span style="color: rgba(0, 0, 0, 1)"> result,
      CancellationToken cancellationToken </span>= <span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">)
      </span>=&gt; <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)">(result);

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 异步版本</span>
    ValueTask&lt;InterceptionResult&lt;<span style="color: rgba(0, 0, 255, 1)">int</span>&gt;&gt;<span style="color: rgba(0, 0, 0, 1)"> NonQueryExecutingAsync(
      DbCommand command,
      CommandEventData eventData,
      InterceptionResult</span>&lt;<span style="color: rgba(0, 0, 255, 1)">int</span>&gt;<span style="color: rgba(0, 0, 0, 1)"> result,
      CancellationToken cancellationToken </span>= <span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">)
      </span>=&gt; <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)">(result);

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 以下是 SQL 语句执行完毕,且从数据库返回结果,但你仍可以处理这些结果</span>
<span style="color: rgba(0, 0, 0, 1)">    DbDataReader ReaderExecuted(DbCommand command, CommandExecutedEventData eventData, DbDataReader result)
      </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> result;
    </span><span style="color: rgba(0, 0, 255, 1)">object</span>? ScalarExecuted(DbCommand command, CommandExecutedEventData eventData, <span style="color: rgba(0, 0, 255, 1)">object</span>?<span style="color: rgba(0, 0, 0, 1)"> result)
      </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> result;
    </span><span style="color: rgba(0, 0, 255, 1)">int</span> NonQueryExecuted(DbCommand command, CommandExecutedEventData eventData, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> result)
      </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> result;

   </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 以下是异步版本</span>
    ValueTask&lt;DbDataReader&gt;<span style="color: rgba(0, 0, 0, 1)"> ReaderExecutedAsync(
      DbCommand command,
      CommandExecutedEventData eventData,
      DbDataReader result,
      CancellationToken cancellationToken </span>= <span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">)
      </span>=&gt; <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)">(result);
    ValueTask</span>&lt;<span style="color: rgba(0, 0, 255, 1)">object</span>?&gt;<span style="color: rgba(0, 0, 0, 1)"> ScalarExecutedAsync(
      DbCommand command,
      CommandExecutedEventData eventData,
      </span><span style="color: rgba(0, 0, 255, 1)">object</span>?<span style="color: rgba(0, 0, 0, 1)"> result,
      CancellationToken cancellationToken </span>= <span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">)
      </span>=&gt; <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)">(result);
    ValueTask</span>&lt;<span style="color: rgba(0, 0, 255, 1)">int</span>&gt;<span style="color: rgba(0, 0, 0, 1)"> NonQueryExecutedAsync(
      DbCommand command,
      CommandExecutedEventData eventData,
      </span><span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> result,
      CancellationToken cancellationToken </span>= <span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">)
      </span>=&gt; <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)">(result);

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 以下是命令被取消或执行失败后调用</span>
    <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> CommandCanceled(DbCommand command, CommandEndEventData eventData)
    {
    }
    Task CommandCanceledAsync(DbCommand command, CommandEndEventData eventData, CancellationToken cancellationToken </span>= <span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">)
      </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> Task.CompletedTask;
    </span><span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> CommandFailed(DbCommand command, CommandErrorEventData eventData)
    {
    }
    Task CommandFailedAsync(DbCommand command, CommandErrorEventData eventData, CancellationToken cancellationToken </span>= <span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">)
      </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> Task.CompletedTask;

   </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 以下是当 dataReader 被关闭前触发</span>
<span style="color: rgba(0, 0, 0, 1)">    InterceptionResult DataReaderClosing(DbCommand command, DataReaderClosingEventData eventData, InterceptionResult result)
      </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> result;
    ValueTask</span>&lt;InterceptionResult&gt;<span style="color: rgba(0, 0, 0, 1)"> DataReaderClosingAsync(DbCommand command, DataReaderClosingEventData eventData, InterceptionResult result)
      </span>=&gt; <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)">(result);

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> dataReader 被释放前触发
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这个最好原样返回,就算你 Suppressed 它,阻止不了连象、命令、阅读器被设为 null
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Suppressed 它纯粹只是在设为 null 前不调用 Dispose 方法罢了</span>
<span style="color: rgba(0, 0, 0, 1)">    InterceptionResult DataReaderDisposing(DbCommand command, DataReaderDisposingEventData eventData, InterceptionResult result)
      </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> result;
}</span></pre>
</div>
<p>你看看,我只是想拦截某个操作,却要实现这么多方法,这太不像话了。为了你觉得像话,EF Core 提供了一个抽象类,叫&nbsp;DbCommandInterceptor,它会以默认行为实现&nbsp;IDbCommandInterceptor 接口。这样你就轻松了,想修改哪个操作,只要重写某个方法成员就好了。</p>
<p>这里,咱们要获取 SQL 语句,只有在&nbsp;CommandInitialized 时 SQL 语句才被设置,所以重写&nbsp;CommandInitialized 方法。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> MyCommandInterceptor : DbCommandInterceptor
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">override</span><span style="color: rgba(0, 0, 0, 1)"> DbCommand CommandInitialized(CommandEndEventData eventData, DbCommand result)
    {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 只获取 LINQ 查询生成的 SQL 语句</span>
      <span style="color: rgba(0, 0, 255, 1)">if</span> (eventData.CommandSource ==<span style="color: rgba(0, 0, 0, 1)"> CommandSource.LinqQuery)
      {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 第一种方法</span>
            Console.WriteLine($<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\nLog Command:\n{<strong><span style="background-color: rgba(255, 255, 0, 1)">eventData.LogCommandText</span></strong>}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 第二种方法</span>
            Console.WriteLine($<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">DB Command:\n{<span style="background-color: rgba(255, 255, 0, 1)"><strong>result.CommandText</strong></span>}\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 第三种方法
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">Console.WriteLine($"From Event Data:\n{<strong><span style="background-color: rgba(255, 255, 0, 1)">eventData.Command.CommandText</span></strong>}\n");</span>
<span style="color: rgba(0, 0, 0, 1)">      }
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">base</span><span style="color: rgba(0, 0, 0, 1)">.CommandInitialized(eventData, result);
    }
}</span></pre>
</div>
<p>其实传入方法的参数里面有些对象是重复的,所以你有多个方法来获取 SQL。eventData.Command 其实就是 result 参数所引用的对象,所以随便哪个的 CommandText 属性都能获取 SQL 语句;另外,eventData 的 LogCommandText 属性也是 SQL 语句。这些方法你随便选一个。</p>
<p>上述代码中,老周用&nbsp;CommandSource.LinqQuery 进行判断,咱们只记下由 LINQ 查询生成的 SQL 语句。</p>
<p>现在回到数据库上下文类,在&nbsp;OnConfiguring 方法中添加刚刚弄好的拦截器。</p>
<div class="cnblogs_code">
<pre>    <span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
      optionsBuilder.UseSqlite(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">data source=demo.db</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
      optionsBuilder.<span style="background-color: rgba(255, 255, 0, 1)"><strong>AddInterceptors(</strong></span></span><span style="background-color: rgba(255, 255, 0, 1)"><strong><span style="color: rgba(0, 0, 255, 1)">new</span></strong></span><span style="color: rgba(0, 0, 0, 1)"><span style="background-color: rgba(255, 255, 0, 1)"><strong> MyCommandInterceptor())</strong></span>;
    }</span></pre>
</div>
<p>调用&nbsp;AddInterceptors 方法,把你想要添加的拦截器实例扔进去就完事了。</p>
<p>再次运行程序,控制台输出以下内容:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">Log Command:
SELECT </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">t</span><span style="color: rgba(128, 0, 0, 1)">"</span>.<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">ID</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">t</span><span style="color: rgba(128, 0, 0, 1)">"</span>.<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Artist</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">t</span><span style="color: rgba(128, 0, 0, 1)">"</span>.<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Duration</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">t</span><span style="color: rgba(128, 0, 0, 1)">"</span>.<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Name</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
FROM </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">tb_songs</span><span style="color: rgba(128, 0, 0, 1)">"</span> AS <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">t</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
WHERE </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">t</span><span style="color: rgba(128, 0, 0, 1)">"</span>.<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">ID</span><span style="color: rgba(128, 0, 0, 1)">"</span> &gt; <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">
DB Command:
SELECT </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">t</span><span style="color: rgba(128, 0, 0, 1)">"</span>.<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">ID</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">t</span><span style="color: rgba(128, 0, 0, 1)">"</span>.<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Artist</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">t</span><span style="color: rgba(128, 0, 0, 1)">"</span>.<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Duration</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">t</span><span style="color: rgba(128, 0, 0, 1)">"</span>.<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Name</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
FROM </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">tb_songs</span><span style="color: rgba(128, 0, 0, 1)">"</span> AS <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">t</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
WHERE </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">t</span><span style="color: rgba(128, 0, 0, 1)">"</span>.<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">ID</span><span style="color: rgba(128, 0, 0, 1)">"</span> &gt; <span style="color: rgba(128, 0, 128, 1)">2</span></pre>
</div>
<p>&nbsp;</p>
<p>-----------------------------------------------------------------------</p>
<p>第三种方法很简单,就是 IQueyable 的扩展方法。直接上示例就行了。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span> <span style="color: rgba(0, 0, 255, 1)">var</span> c = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MyDbContext();
</span><span style="color: rgba(0, 0, 255, 1)">var</span> q = <span style="color: rgba(0, 0, 255, 1)">from</span> s <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> c.Songs
             </span><span style="color: rgba(0, 0, 255, 1)">where</span> s.Artist != <span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp; s.Artist.StartsWith(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">L</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
             </span><span style="color: rgba(0, 0, 255, 1)">select</span><span style="color: rgba(0, 0, 0, 1)"> s;
</span><span style="color: rgba(0, 0, 255, 1)">string</span> sql =<span style="color: rgba(0, 0, 0, 1)"> q.<strong><span style="background-color: rgba(255, 255, 0, 1)">ToQueryString</span></strong>();
Console.WriteLine(sql);</span></pre>
</div>
<p>运行结果如下:</p>
<div class="cnblogs_code">
<pre>SELECT <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">t</span><span style="color: rgba(128, 0, 0, 1)">"</span>.<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">ID</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">t</span><span style="color: rgba(128, 0, 0, 1)">"</span>.<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Artist</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">t</span><span style="color: rgba(128, 0, 0, 1)">"</span>.<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Duration</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">t</span><span style="color: rgba(128, 0, 0, 1)">"</span>.<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Name</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
FROM </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">tb_songs</span><span style="color: rgba(128, 0, 0, 1)">"</span> AS <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">t</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
WHERE </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">t</span><span style="color: rgba(128, 0, 0, 1)">"</span>.<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Artist</span><span style="color: rgba(128, 0, 0, 1)">"</span> IS NOT NULL AND <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">t</span><span style="color: rgba(128, 0, 0, 1)">"</span>.<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Artist</span><span style="color: rgba(128, 0, 0, 1)">"</span> LIKE <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">L%</span><span style="color: rgba(128, 0, 0, 1)">'</span></pre>
</div>
<p>此方法的缺点是只能获取到查询语句,插入、删除等语句不能获取。</p>
<p>&nbsp;</p>
<p>好了,今天的内容就到这里完毕了,下次老周继续聊 EF Core。这是老周的习惯,抓住一个主题聊他个天荒地老。</p>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/tcjiaan/p/19288409
頁: [1]
查看完整版本: 【EF Core】三种方法记录生成的 SQL 语句