在最近的幾個 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. uboot 修改代码 增加 环境变量

    --- title: uboot修改代码增加环境变量 date: 2019-12-27 21:26:39 categories: tags: - uboot --- 以"tftp下载kern ...

  2. 动手学Avalonia:基于SemanticKernel与硅基流动构建AI聊天与翻译工具

    Avalonia是什么? Avalonia是一个跨平台的UI框架,专为.NET开发打造,提供灵活的样式系统,支持Windows.macOS.Linux.iOS.Android及WebAssembly等 ...

  3. 使用flume将数据sink到HBase

    ===========>先创建Hbase表和列族<================案例1:源数据一行对应Hbase的一列存储(hbase-1.12没有问题)================ ...

  4. ABC195E

    其实我们发现很多博弈论的动态规划都是从后往前的,比如过河卒和本题. 这是因为从某种角度上来说这些动态规划有后效性而无前效性. 所以设计状态 \(dp_{i,j}\) 表示第 \(i\) 次操作 \(T ...

  5. P3731 题解

    简要题意是找到一条边连接使得最大团大小增加. 在补图上最大团等于最大独立集. 所以问题转化为删掉一条边使得最大独立集增加,又因为团不超过两个,所以原图是二分图,也就是使得最大匹配减少. 考虑什么样的匹 ...

  6. 四 黑马程序员-java面向对象(上)

    一.:面向对象 (1)面向对象:是基于面向过程的一种思想. 面向过程:以函数为基础,关注实现过程. 面向对象:以对象为基础,关注实现结果. (2)面向对象的思想特点: A:是一种更符合人们思考习惯的思 ...

  7. 容器技术Docker知识精讲【形成知识体系篇】

    作者的经验分享,包括很多实战过程和总结,为着手系统化学习Docker容器的朋友提供. 环境要求 Linux操作系统(Centos),建议在虚拟机VMware或VirtualBox下安装Centos D ...

  8. vue项目锚点定位+滚动定位

    功能: HTML: js: scrollEvent(e) { let scrollItems = document.querySelectorAll('.condition-container') f ...

  9. 人脸识别项目打包成exe的过程遇到的问题

    我最近重新拾起了计算机视觉,借助Python的opencv还有face_recognition库写了个简单的图像识别demo,额外定制了一些内容,原本想打包成exe然后发给朋友,不过在这当中遇到了许多 ...

  10. 《最新出炉》系列初窥篇-Python+Playwright自动化测试-61 - 隐藏元素定位与操作

    1.简介 对于前端隐藏元素,一直是自动化定位元素的隐形杀手,让人防不胜防.脚本跑到隐藏元素时位置时报各种各样的错误,可是这种隐藏的下拉菜单又没有办法避免,所以非常头痛,这一篇只为交流隐藏元素自动化定位 ...