ASP.NET Core 如何紀錄 Entity Framework Core 5.0 自動產生的 SQL 命令
在最近的幾個 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 都可以用。基本上你只需要設定兩個步驟:
- 編輯 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 的參數值,就要進一步調整程式碼。
- 編輯 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 命令的更多相关文章
- 请问在 .NET Core 中如何让 Entity Framework Core 在日志中记录由 LINQ 生成的SQL语句?
using dotNET.Core; using Microsoft.Extensions.Logging; using System; using System.Collections.Generi ...
- Entity Framework Core 练习参考
项目地址:https://gitee.com/dhclly/IceDog.EFCore 项目介绍 对 Microsoft EntityFramework Core 框架的练习测试 参考文档教程 官方文 ...
- 全自动迁移数据库的实现 (Fluent NHibernate, Entity Framework Core)
在开发涉及到数据库的程序时,常会遇到一开始设计的结构不能满足需求需要再添加新字段或新表的情况,这时就需要进行数据库迁移. 实现数据库迁移有很多种办法,从手动管理各个版本的ddl脚本,到实现自己的mig ...
- 创建ASP.NET Core MVC应用程序(3)-基于Entity Framework Core(Code First)创建MySQL数据库表
创建ASP.NET Core MVC应用程序(3)-基于Entity Framework Core(Code First)创建MySQL数据库表 创建数据模型类(POCO类) 在Models文件夹下添 ...
- 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 ...
- 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 ...
- 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 ...
- 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 ...
- 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 ...
- 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 ...
随机推荐
- 『vulnhub系列』HACKABLE-II
『vulnhub系列』HACKABLE-II 下载地址: https://www.vulnhub.com/entry/hackable-ii,711/ 信息搜集: 使用nmap探测存活主机,发现主机开 ...
- 记一次aspnetcore发布部署流程初次使用k8s
主题: aspnetcorewebapi项目,提交到gitlab,通过jenkins(gitlab的ci/cd)编译.发布.推送到k8s. 关于gitlab.jenkins.k8s安装,都是使用doc ...
- 一个用来画拉氏图的简单Python脚本
技术背景 关于拉氏图的更多介绍,可以参考下这篇博客,这里简单引述一部分内容: Ramachandran plot(拉氏图)是由G. N. Ramachandran等人于1963年开发的,用来描述蛋白质 ...
- python3 socket 获取域名信息
可以当ping用,应用场景可以在一些没有安装ping工具的镜像容器中,诊断dns或域名的可用性. #-*- coding:utf-8 -*- import socket import tracebac ...
- mac电脑好用的工具总结
制作gif:https://gfycat.com/gifbrewery 制作gif(超级好用,制作速度快,压缩图片小):https://www.cockos.com/licecap/ 解压工具:htt ...
- react-devtools安装以及使用中的问题
使用react框架开发的小伙伴肯定都想使用想vue-devtools开发工具一样,可以看见组件的状态,和当前组件里的props,data等等. 当然react也有一个开发者工具,现在废话少说,开始安装 ...
- opc ua设备数据 转MQTT项目案例
目录 1 案例说明 1 2 VFBOX网关工作原理 1 3 准备工作 2 4 配置VFBOX网关采集OPC UA的数据 2 5 用MQTT协议转发数据 4 6 配置参数说明 4 7 上报内容配置 5 ...
- 洛谷P1464
搜索优化后是记忆化搜索,再优化就是dp了 #include <iostream> #include <utility> using namespace std; typedef ...
- 解决方案 | pyautogui实现等待特定图片(对话框)出现(可设置等待超时时间)
1.问题 为了等待某个[转换完毕]的图片(对话框)出现,如何设置? 2.解决方案代码如下 下面代码实现的是:设置超时时间为10s,当你在完成前序工作以后,代码开始进入等待,一旦你的特定图片出现了,马上 ...
- 5 pdf页码跳转失效
PC端网页查看pdf时,输入非数字页码回车后,页码跳转功能失效