在最近的幾個 Entity Framework Core 版本,對於 Logging (紀錄) 的撰寫方式一直在改變,大致上可區分成 EF Core 2.1 , EF Core 3.0+ 與 EF Core 5.0 三種版本,這意味著你從網路上找到的資訊,很有可能是舊的,但很多人無法區別其差異,因為舊的寫法到了 EF Core 5.0 依然可以用,只是會被標示棄用而已。你應該也可以想像得到,越新的 EF Core 版本,寫法就會更簡單的,這篇文章我就來說說幾種不同的紀錄方法。

為什麼要紀錄 EF Core 自動產生的 SQL 命令

由於 Entity Framework Core 是一套相當完整的 ORM 框架,我們可以利用 ORM 架構幫我們對資料進行建模(Modeling),用「物件導向」的方式描述一份資料,方便我們可以用相對「抽象」的方式來存取資料。不過,有些時候透過 ORM 框架自動產生的 SQL 語法,可能會導致執行效能低落,這時我們就可以透過 Logging 的方式,將 EF Core 自動產生的 SQL 命令印出來(或寫到檔案中),藉此進一步分析如何優化這些 SQL 語法的執行效率。

我們在做 Entity Framework 效能調教的時候,比較常見的方法是將複雜的查詢語法,改用預存程序(Stored Procedure)或檢視表(Views)來改善執行效率,但更多時候可能只是加入「索引」就可以大幅改善查詢效能。如果遇到需要批次新增、更新或刪除資料的情況,也會透過直接執行 SQL 的方式來執行,封裝成預存程序也是另一種可行的方案。

最簡單的紀錄方法

其實要紀錄 EF Core 自動產生的 SQL 命令,最簡單的方式,就是直接調整 Logging 的組態設定,而且從舊版 EF Core 到最新的 EF Core 5.0 都可以用。基本上你只需要設定兩個步驟:

  1. 編輯 appsettings.Development.json 組態設定

你只要在 Logging:LogLevel 加入 Microsoft.EntityFrameworkCore.Database.Command 並將其設定為 Information 即可:

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
}
}

假設我們對 Course 實體查詢的程式碼如下:

var course = await _context.Courses.FindAsync(1));

預設輸出的 Log 內容,並不會包含實際傳到資料庫的查詢參數值( 1 ),只有 SQL 語法而已,這是因為 個資保護法 造成的變化:

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (35ms) [Parameters=[@__p_0='?'], CommandType='Text', CommandTimeout='30']
SELECT TOP(1) [c].[CourseID], [c].[Credits], [c].[DepartmentID], [c].[Title]
FROM [Course] AS [c]
WHERE [c].[CourseID] = @__p_0

要顯示上述 @__p_0 的參數值,就要進一步調整程式碼。

  1. 編輯 Startup 類別的 ConfigureServices 方法

請將以下這段程式:

services.AddDbContext<ContosoUniversityContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
});

修改為以下,也就是加上 options.EnableSensitiveDataLogging(); 設定:

services.AddDbContext<ContosoUniversityContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
options.EnableSensitiveDataLogging();
});

啟用「敏感資料」紀錄的結果,你的紀錄內容就會變成這樣:

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (35ms) [Parameters=[@__p_0='1'], CommandType='Text', CommandTimeout='30']
SELECT TOP(1) [c].[CourseID], [c].[Credits], [c].[DepartmentID], [c].[Title]
FROM [Course] AS [c]
WHERE [c].[CourseID] = @__p_0

簡單兩個步驟,真的蠻實用的,各位可以參考看看!

使用 Simple Logging 方法

EF Core 5.0新增一個 Simple Logging 機制,可以用相當簡易的方式紀錄 EF Core 執行的過程。這個機制跟我們在 EF6 的時候使用的 db.Database.Log 很像,但有點不太一樣。舊的 EF6 只能單純紀錄 ORM 轉譯後 SQL 語法,但 EF Core 5.0 卻還可以紀錄各種 EF Core 執行過程產生的各種事件資料。

設定方式,就是修改你在 Startup 類別的 ConfigureServices 方法,將以下這段程式:

services.AddDbContext<ContosoUniversityContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
options.EnableSensitiveDataLogging();
});

修改為以下即可:

services.AddDbContext<ContosoUniversityContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
options.EnableSensitiveDataLogging();
options.LogTo(Console.WriteLine);
});

你可以從上述範例程式看到,其實 options.LogTo() 的使用方式跟 EF6 的 db.Database.Log() 相當類似,也很容易上手,反正傳入一個 Action 就可以用任何自訂的邏輯來紀錄 EF Core 執行的過程。(參考: Action Delegate )

不過,如果你真的看了 Log 輸出的結果,你會發現這份紀錄比你想像的還複雜很多,因為紀錄的內容真的很多:

warn: Microsoft.EntityFrameworkCore.Model.Validation[10400]
Sensitive data logging is enabled. Log entries and exception messages may include sensitive application data; this mode should only be enabled during development.
warn: 2020/12/13 01:00:00.008 CoreEventId.SensitiveDataLoggingEnabledWarning[10400] (Microsoft.EntityFrameworkCore.Infrastructure)
Sensitive data logging is enabled. Log entries and exception messages may include sensitive application data; this mode should only be enabled during development.
info: 2020/12/13 01:00:00.111 CoreEventId.ContextInitialized[10403] (Microsoft.EntityFrameworkCore.Infrastructure)
Entity Framework Core 5.0.0 initialized 'ContosoUniversityContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: SensitiveDataLoggingEnabled
dbug: 2020/12/13 01:00:00.165 CoreEventId.QueryCompilationStarting[10111] (Microsoft.EntityFrameworkCore.Query)
Compiling query expression:
'DbSet<Course>()
.FirstOrDefault(e => EF.Property<int>(e, "CourseId") == __p_0)'
dbug: 2020/12/13 01:00:00.278 CoreEventId.QueryExecutionPlanned[10107] (Microsoft.EntityFrameworkCore.Query)
Generated query execution expression:
'queryContext => ShapedQueryCompilingExpressionVisitor.SingleOrDefaultAsync<Course>(
asyncEnumerable: new SingleQueryingEnumerable<Course>(
(RelationalQueryContext)queryContext,
RelationalCommandCache.SelectExpression(
Projection Mapping:
EmptyProjectionMember -> Dictionary<IProperty, int> { [Property: Course.CourseId (int) Required PK AfterSave:Throw ValueGenerated.OnAdd, 0], [Property: Course.Credits (int) Required, 1], [Property: Course.DepartmentId (int) Required FK Index ValueGenerated.OnAdd, 2], [Property: Course.Title (string) MaxLength(50), 3], }
SELECT TOP(1) c.CourseID, c.Credits, c.DepartmentID, c.Title
FROM Course AS c
WHERE c.CourseID == @__p_0),
Func<QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator, Course>,
WebApplication4.Models.ContosoUniversityContext,
False,
False
),
cancellationToken: queryContext.CancellationToken)'
dbug: 2020/12/13 01:00:00.323 RelationalEventId.CommandCreating[20103] (Microsoft.EntityFrameworkCore.Database.Command)
Creating DbCommand for 'ExecuteReader'.
dbug: 2020/12/13 01:00:00.327 RelationalEventId.CommandCreated[20104] (Microsoft.EntityFrameworkCore.Database.Command)
Created DbCommand for 'ExecuteReader' (18ms).
dbug: 2020/12/13 01:00:00.336 RelationalEventId.ConnectionOpening[20000] (Microsoft.EntityFrameworkCore.Database.Connection)
Opening connection to database 'ContosoUniversity' on server '(localdb)\MSSQLLocalDB'.
dbug: 2020/12/13 01:00:00.492 RelationalEventId.ConnectionOpened[20001] (Microsoft.EntityFrameworkCore.Database.Connection)
Opened connection to database 'ContosoUniversity' on server '(localdb)\MSSQLLocalDB'.
dbug: 2020/12/13 01:00:00.498 RelationalEventId.CommandExecuting[20100] (Microsoft.EntityFrameworkCore.Database.Command)
Executing DbCommand [Parameters=[@__p_0='1'], CommandType='Text', CommandTimeout='30']
SELECT TOP(1) [c].[CourseID], [c].[Credits], [c].[DepartmentID], [c].[Title]
FROM [Course] AS [c]
WHERE [c].[CourseID] = @__p_0
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (37ms) [Parameters=[@__p_0='1'], CommandType='Text', CommandTimeout='30']
SELECT TOP(1) [c].[CourseID], [c].[Credits], [c].[DepartmentID], [c].[Title]
FROM [Course] AS [c]
WHERE [c].[CourseID] = @__p_0
info: 2020/12/13 01:00:00.535 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (37ms) [Parameters=[@__p_0='1'], CommandType='Text', CommandTimeout='30']
SELECT TOP(1) [c].[CourseID], [c].[Credits], [c].[DepartmentID], [c].[Title]
FROM [Course] AS [c]
WHERE [c].[CourseID] = @__p_0
dbug: 2020/12/13 01:00:00.564 CoreEventId.StartedTracking[10806] (Microsoft.EntityFrameworkCore.ChangeTracking)
Context 'ContosoUniversityContext' started tracking 'Course' entity with key '{CourseId: 1}'.
dbug: 2020/12/13 01:00:00.584 RelationalEventId.DataReaderDisposing[20300] (Microsoft.EntityFrameworkCore.Database.Command)
A data reader was disposed.
dbug: 2020/12/13 01:00:00.587 RelationalEventId.ConnectionClosing[20002] (Microsoft.EntityFrameworkCore.Database.Connection)
Closing connection to database 'ContosoUniversity' on server '(localdb)\MSSQLLocalDB'.
dbug: 2020/12/13 01:00:00.590 RelationalEventId.ConnectionClosed[20003] (Microsoft.EntityFrameworkCore.Database.Connection)
Closed connection to database 'ContosoUniversity' on server '(localdb)\MSSQLLocalDB'.
dbug: 2020/12/13 01:00:00.603 CoreEventId.ContextDisposed[10407] (Microsoft.EntityFrameworkCore.Infrastructure)
'ContosoUniversityContext' disposed.

很多時候我們還真的只想要 SQL 語法而已,此時你有兩種降低紀錄詳細程度的方法:

  • 設定紀錄等級 ( LogLevel )
options.LogTo(Console.WriteLine, LogLevel.Information);
  • 設定紀錄等級並篩選紀錄類別
options.LogTo(Console.WriteLine, new[] { DbLoggerCategory.Database.Name }, LogLevel.Information);

這個設定就可以讓我們特別篩選出 Microsoft.EntityFrameworkCore.Database.Command 紀錄類別就好。

你當然不止可以紀錄到 Console 裡,也可以輸出到檔案中,你只要到 DbContext 類別 (例如 ContosoUniversityContext 類別) 中加入以下程式碼即可:

private readonly StreamWriter _logStream = new StreamWriter("EFCoreDebug.log", append: true);
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(_logStream.WriteLine);
public override void Dispose()
{
base.Dispose();
_logStream.Dispose();
}
public override async ValueTask DisposeAsync()
{
await base.DisposeAsync();
await _logStream.DisposeAsync();
}

請注意:你可以將本文所提到的設定寫在 DbContext 的 OnConfiguring() 方法或 Startup 的 ConfigureServices 方法中。

ASP.NET Core 如何紀錄 Entity Framework Core 5.0 自動產生的 SQL 命令的更多相关文章

  1. 请问在 .NET Core 中如何让 Entity Framework Core 在日志中记录由 LINQ 生成的SQL语句?

    using dotNET.Core; using Microsoft.Extensions.Logging; using System; using System.Collections.Generi ...

  2. Entity Framework Core 练习参考

    项目地址:https://gitee.com/dhclly/IceDog.EFCore 项目介绍 对 Microsoft EntityFramework Core 框架的练习测试 参考文档教程 官方文 ...

  3. 全自动迁移数据库的实现 (Fluent NHibernate, Entity Framework Core)

    在开发涉及到数据库的程序时,常会遇到一开始设计的结构不能满足需求需要再添加新字段或新表的情况,这时就需要进行数据库迁移. 实现数据库迁移有很多种办法,从手动管理各个版本的ddl脚本,到实现自己的mig ...

  4. 创建ASP.NET Core MVC应用程序(3)-基于Entity Framework Core(Code First)创建MySQL数据库表

    创建ASP.NET Core MVC应用程序(3)-基于Entity Framework Core(Code First)创建MySQL数据库表 创建数据模型类(POCO类) 在Models文件夹下添 ...

  5. Working with Data » Getting started with ASP.NET Core and Entity Framework Core using Visual Studio » 更新关系数据

    Updating related data¶ 7 of 7 people found this helpful The Contoso University sample web applicatio ...

  6. Working with Data » Getting started with ASP.NET Core and Entity Framework Core using Visual Studio » 读取关系数据

    Reading related data¶ 9 of 9 people found this helpful The Contoso University sample web application ...

  7. Working with Data » Getting started with ASP.NET Core and Entity Framework Core using Visual Studio »迁移

    Migrations¶ 4 of 4 people found this helpful The Contoso University sample web application demonstra ...

  8. Working with Data » Getting started with ASP.NET Core and Entity Framework Core using Visual Studio » 创建复杂数据模型

    Creating a complex data model 创建复杂数据模型 8 of 9 people found this helpful The Contoso University sampl ...

  9. Working with Data » Getting started with ASP.NET Core and Entity Framework Core using Visual Studio » 排序、筛选、分页以及分组

    Sorting, filtering, paging, and grouping 7 of 8 people found this helpful By Tom Dykstra The Contoso ...

  10. Working with Data » Getting started with ASP.NET Core and Entity Framework Core using Visual Studio » 增、查、改、删操作

    Create, Read, Update, and Delete operations¶ 5 of 5 people found this helpful By Tom Dykstra The Con ...

随机推荐

  1. ​Chrome插件:Postman Interceptor 调试的终极利器

    今天给大家介绍一款非常实用的工具--Postman Interceptor. 这个工具可以捕捉任何网站的请求,并将其发送到Postman客户端. 对于经常和API打交道的程序员来说,Postman I ...

  2. 3568F-评估板测试手册(ARM端)

     

  3. 【ClickHouse】2:clickhouse基本语法

    背景介绍: 有三台CentOS7服务器安装了ClickHouse HostName IP 安装程序 程序端口 centf8118.sharding1.db 192.168.81.18 clickhou ...

  4. c# 对DataTable数据筛选后进行修改操作

    记录一次对DataTable中的数据筛选去重后,然后对数据进行修改! foreach (DataRow dr in dt.Rows) // 便利dt { if (StringUtil.isNotNul ...

  5. react懒加载,减少首屏加载时间

    最近在写一个react-ant-admin的集成框架用于快速搭载中后台项目.其中遇到很多问题,最重要的应该是访问速度了.我就想 react 可不可以和 vue 一样用路由懒加载来减少首页渲染所花费的时 ...

  6. CF2B

    非常恶心的dp题,测试数据也恶心,坑多,特别是0的坑 #include <iostream> #include <utility> #include <string> ...

  7. 基于EF Core存储的国际化服务

    前言 .NET 官方有一个用来管理国际化资源的扩展包Microsoft.Extensions.Localization,ASP.NET Core也用这个来实现国际化功能.但是这个包的翻译数据是使用re ...

  8. laravel6学习

    web 服务器需要拥有 storage 目录下的所有目录和 bootstrap/cache 目录的写权限

  9. 浅谈 I/O 与 I/O 多路复用

    1.基础知识 网络编程里常听到阻塞IO.非阻塞IO.同步IO.异步IO等概念,总听别人聊不如自己下来钻研一下.不过,搞清楚这些概念之前,还得先回顾一些基础的概念. 下面说的都是Linux环境下,跟Wi ...

  10. scratch源码下载 | 蜘蛛传说

    程序说明: <蜘蛛传说>是一个通过Scratch平台制作的互动游戏项目.在这个故事中,玩家将扮演一只蜘蛛,其原本和平的生活被一只入侵的壁虎所打破.为了保卫自己的家园,蜘蛛必须运用智慧和勇气 ...