EF Core 中需通过 ILogger 监听 Microsoft.EntityFrameworkCore.Database.Command 类别日志,捕获 CoreEventId.CommandExecuting 事件获取 SQL 与参数,手动替换 @p0 等占位符为实际值(注意类型格式),Debug/Information 级别才输出,生产环境应禁用。

EF Core 里怎么拿到正在执行的 SQL 语句
直接看 DbContext.Database.Log?别用了,那是 EF6 的旧接口,EF Core 已移除。现在得靠 ILogger 或 IDiagnosticsLogger 拦截日志,核心是监听 Microsoft.EntityFrameworkCore.Database.Command 类别下的日志事件。
常见错误现象:Console.WriteLine 打印出一堆“Executed DbCommand”,但没 SQL;或者只看到参数占位符(如 @p0),看不到实际值。
- 必须启用日志级别为
Debug或Information,Warning及以上默认不输出 SQL - EF Core 默认把 SQL 和参数分开记录——SQL 在一条日志里,参数在下一条,需要手动关联(比如用同一
commandId) - 如果用的是
UseSqlServer等 provider,SQL 中的参数名(如@p0)不会自动替换为真实值,需额外解析DbParameter集合
如何用 ILogger 拦截并拼出完整可执行 SQL
不是改 DbContext 构造函数,而是注册自定义 ILoggerProvider,或更轻量地重写 ILogger 实现。关键点在于识别日志事件 ID:CoreEventId.CommandExecuting 对应 SQL 字符串,CoreEventId.CommandExecuted 包含耗时,但参数得从 EventData 的 Command 属性里取。
使用场景:调试慢查询、审计敏感操作、导出测试用例 SQL。
- 不要依赖日志文本拼接——不同 provider 输出格式不同(SQLite 用
?占位,SQL Server 用@p0) - 真实值替换要小心类型:字符串加单引号、
null写成NULL、DateTime格式化为 ISO8601(如'2024-05-20T10:30:00') - 性能影响明显:每条命令触发两次日志(执行前/后),生产环境务必关闭或仅对特定 DbContext 启用
public class SqlCaptureLogger : ILogger
{
public void Log<TState>(LogLevel logLevel, EventId eventId,
EventSource eventSource, TState state, Exception exception,
Func<TState, Exception, string> formatter)
{
if (eventId == CoreEventId.CommandExecuting)
{
var command = (RelationalCommandEventData)state;
var sql = command.Command.CommandText;
// 此处遍历 command.Command.Parameters 获取参数值并替换
}
}
}
EF6 怎么简单获取 SQL(别和 EF Core 混了)
EF6 还能用 DbContext.Database.Log,但注意它只对 ToList()、ToArray() 等触发查询的操作有效,对 IQueryable 构建阶段无效。而且它输出的是带换行缩进的美化 SQL,不能直接复制执行。
容易踩的坑:Database.Log 是实例级设置,每次 new DbContext 都得重新赋值;Lambda 表达式里写 Console.WriteLine 看起来方便,但上线后容易被当成调试残留漏删。
- 别在
ToString()上浪费时间——IQueryable.ToString()只返回表达式树描述,不是 SQL - 如果用的是
ObjectQuery.ToTraceString()(老版 Entity Framework),那说明你还在用 EF 4.x,语法和 EF6 不兼容 - SQL Server Profiler 或 Azure Data Studio 的实时查询监控,比代码拦截更可靠,尤其当你要确认最终发到数据库的是什么
为什么不能直接 hook SqlCommand 或 DbCommand
EF 内部用的是 RelationalCommand 封装,不是裸的 SqlCommand。想在 ExecuteReader 前拿到 SQL,得绕过 EF 的命令编译流程,代价高、易崩溃,且跨 provider 不通用。
真实限制:EF Core 6+ 引入了 IDbContextOptionsExtension 机制,理论上可插拔命令生成器,但文档极少、API 不稳定,属于“能做但不该做”的范畴。
- 第三方库如
EFCore.SqlServerCompact或EFCore.BulkExtensions可能覆盖底层命令逻辑,你的 hook 可能失效 - 异步方法(
ToListAsync)的日志事件 ID 和同步不同,需同时监听CommandExecutingAsync - 最稳的方式其实是开 SQL Server 的
QUERY_STORE或 PostgreSQL 的log_statement = 'all',而不是在应用层硬捞
真正难的不是“怎么拿到 SQL”,而是“拿到之后怎么安全地还原成可执行语句”——参数类型、时区、NOCOUNT 设置、事务隔离级别,这些上下文一丢,复制粘贴过去很可能报错。










