我在 MVC 5 项目中使用 EF 6.0 和 LINQ。我想记录实体框架 DbContext 执行的所有 SQL 查询,以用于调试/性能测量目的。
在 Java/Hibernate 中,可以通过设置属性
hibernate.show_sql=true
来实现等效行为。实体框架中是否可能有类似的行为?
记录和拦截数据库操作 MSDN 上的文章就是您正在寻找的内容。
DbContext.Database.Log
属性可以设置为任何采用字符串的方法的委托。最常见的是,它通过将其设置为该 TextWriter 的“Write”方法来与任何 TextWriter
一起使用。当前上下文生成的所有 SQL 都将记录到该编写器。例如,以下代码会将 SQL 记录到控制台:
using (var context = new BlogContext())
{
context.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
// Your code here...
}
更新 2022:现在在 ef core 的开发中默认启用日志记录。可以在数据库上下文的构建器中配置该行为(启用敏感数据日志记录以记录查询参数值或指定要记录的事件):
services.AddDbContext<IDbContext, ApplicationDbContext>(
options => options.UseSqlServer(dbConnectionString)
.LogTo(s => System.Diagnostics.Debug.WriteLine(s))
.EnableDetailedErrors(isDebugMode)
.EnableSensitiveDataLogging(isDebugMode));
或者您可以使用 app.settings 文件来定义您感兴趣的事件的日志记录配置
public class YourContext : DbContext
{
public YourContext()
{
Database.Log = sql => Debug.Write(sql);
}
}
您只需在应用程序设置中配置“信息”日志级别即可。例如,这将 EF 日志记录到调试输出窗口:
"Logging": {
"PathFormat": "Logs/log-{Date}.txt",
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Information",
"System": "Information",
"Microsoft": "Information"
}
},
"Console": {
"LogLevel": {
"Default": "Information",
"System": "Warning",
"Microsoft": "Warning"
}
},
"File": {
"LogLevel": {
"Default": "Information",
"System": "Warning",
"Microsoft": "Warning"
}
},
"LogLevel": {
"Default": "Information",
"System": "Warning",
"Microsoft": "Warning"
}
}
public class SchoolContext : DbContext
{
//static LoggerFactory object
public static readonly ILoggerFactory loggerFactory = new LoggerFactory(new[] {
new ConsoleLoggerProvider((_, __) => true, true)
});
//or
// public static readonly ILoggerFactory loggerFactory = new LoggerFactory().AddConsole((_,___) => true);
public SchoolContext():base()
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLoggerFactory(loggerFactory) //tie-up DbContext with LoggerFactory object
.EnableSensitiveDataLogging()
.UseSqlServer(@"Server=.\SQLEXPRESS;Database=SchoolDB;Trusted_Connection=True;");
}
public DbSet<Student> Students { get; set; }
}
如果您想登录到输出窗口,请使用此选项:
public static readonly ILoggerFactory loggerFactory = new LoggerFactory(new[] {
new DebugLoggerProvider()
});
https://www.entityframeworktutorial.net/efcore/logging-in-entityframework-core.aspx
OnConfiguring
中的
YourDbContext
方法:
public class YourContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.LogTo(Console.WriteLine);
}
}
https://learn.microsoft.com/en-us/ef/ef6/fundamentals/configuring/config-file#interceptors-ef61-onwards
示例代码
<interceptors>
<interceptor type="System.Data.Entity.Infrastructure.Interception.DatabaseLogger, EntityFramework">
<parameters>
<parameter value="C:\Stuff\LogOutput.txt"/>
<parameter value="true" type="System.Boolean"/>
</parameters>
</interceptor>
</interceptors>
var loggerFactory = LoggerFactory.Create(builder =>
{
builder
.AddConsole((options) => { })
.AddFilter((category, level) =>
category == DbLoggerCategory.Database.Command.Name
&& level == LogLevel.Information);
});
在OnConfiguring方法中告诉DbContext使用工厂:
optionsBuilder.UseLoggerFactory(_loggerFactory);
IDbCommandInterceptor
,这是一个完整的示例:
// Created By [email protected] at 2024-11-16 14:17:52+0800
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Common;
using System.Text.RegularExpressions;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Newtonsoft.Json;
using NLog;
using NLog.Config;
using NLog.Targets;
const string layout = "[${date}] [${level:uppercase=true:padding=5}] [${threadid:padding=2}]" +
" [${logger}:${callsite-linenumber}] ${message:withexception=true}";
LogManager.Configuration = new LoggingConfiguration();
LogManager.Configuration.AddRuleForAllLevels(new ConsoleTarget { Layout = layout });
LogManager.Configuration = LogManager.Configuration;
var logger = LogManager.GetLogger(nameof(Program));
var db = new MyDbContext();
await db.Database.EnsureCreatedAsync();
await db.Persons.AddAsync(new Person { Name = "Alpha" });
await db.Persons.AddAsync(new Person { Name = "Beta" });
await db.SaveChangesAsync();
var persons = await db.Persons.Where(x => x.Id > 0).ToListAsync();
logger.Info("Persons: {0}", JsonConvert.SerializeObject(persons));
internal class MyDbContext : DbContext
{
public DbSet<Person> Persons { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var conn = new SqliteConnection("DataSource=:memory:");
conn.Open();
base.OnConfiguring(optionsBuilder.UseSqlite(conn).AddInterceptors(new SqlInterceptor()));
}
}
[Table("person")]
[Index(nameof(Name), IsUnique = true)]
internal class Person
{
[Column("id")] [Key] public int Id { get; set; }
[Column("name")] public string Name { get; set; }
}
internal static partial class MyObjectUtils
{
public static string PrettyPrint(this DbCommand @this)
{
var parameters = string.Join(", ",
@this.Parameters.OfType<DbParameter>().Select(x => $"{x.ParameterName}={x.Value}"));
var command = PrettySpaceRegex().Replace(@this.CommandText, " ");
return @this.Parameters.Count == 0 ? command : $"{command} [{parameters}]";
}
[GeneratedRegex(@"\s+")]
private static partial Regex PrettySpaceRegex();
}
internal class SqlInterceptor : IDbCommandInterceptor
{
private readonly Logger _logger = LogManager.GetLogger(nameof(SqlInterceptor));
public ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command,
CommandEventData eventData, InterceptionResult<DbDataReader> result,
CancellationToken cancellationToken = new())
{
_logger.Info("[R] {0}", command.PrettyPrint());
return new ValueTask<InterceptionResult<DbDataReader>>(result);
}
public ValueTask<InterceptionResult<object>> ScalarExecutingAsync(DbCommand command, CommandEventData eventData,
InterceptionResult<object> result, CancellationToken cancellationToken = new())
{
_logger.Info("[S] {0}", command.PrettyPrint());
return new ValueTask<InterceptionResult<object>>(result);
}
public ValueTask<InterceptionResult<int>> NonQueryExecutingAsync(DbCommand command, CommandEventData eventData,
InterceptionResult<int> result, CancellationToken cancellationToken = new())
{
_logger.Info("[N] {0}", command.PrettyPrint());
return new ValueTask<InterceptionResult<int>>(result);
}
}
输出示例:
[2024/11/16 14:17:12.478] [ INFO] [ 1] [SqlInterceptor:86] [N] CREATE TABLE "person" ( "id" INTEGER NOT NULL CONSTRAINT "PK_person" PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL );
[2024/11/16 14:17:12.504] [ INFO] [ 1] [SqlInterceptor:86] [N] CREATE UNIQUE INDEX "IX_person_name" ON "person" ("name");
[2024/11/16 14:17:12.644] [ INFO] [ 1] [SqlInterceptor:72] [R] INSERT INTO "person" ("name") VALUES (@p0) RETURNING "id"; [@p0=Alpha]
[2024/11/16 14:17:12.660] [ INFO] [ 1] [SqlInterceptor:72] [R] INSERT INTO "person" ("name") VALUES (@p0) RETURNING "id"; [@p0=Beta]
[2024/11/16 14:17:12.952] [ INFO] [ 1] [SqlInterceptor:72] [R] SELECT "p"."id", "p"."name" FROM "person" AS "p" WHERE "p"."id" > 0
[2024/11/16 14:17:13.005] [ INFO] [ 1] [Program:28] Persons: [{"Id":1,"Name":"Alpha"},{"Id":2,"Name":"Beta"}]