You can use IDbCommandInterceptor too, here is a complete example:
// 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);
}
}
Example output:
[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"}]