Как использовать перехватчик команд EF для сохранения данных в базу данных, когда перехваченная команда находится внутри транзакции

#entity-framework #transactions #interceptor

Вопрос:

Я использую перехватчик команд EF для сохранения некоторых данных в базе данных после выполнения команды. (У меня есть некоторые фильтры, чтобы запускать это только в тех командах, которые я хочу, чтобы это не превратилось в бесконечный цикл)

     class EFCommandInterceptor : IDbCommandInterceptor
    {
        public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
            var log = new LogModel
            {
                DateTime = DateTime.Now,
                Log = "Log"
            };

            using (var context = new ApplicationDbContext())
            {
                context.Logs.Add(log);
                var res = context.SaveChangesAsync().Resu<
            }
        }
    }
 

В большинстве случаев это работает нормально. Но если у меня есть какие-либо команды, которые мне нужно зарегистрировать, которые находятся внутри транзакции

 using (TransactionScope transaction = new TransactionScope())
{
    // Some commands to intercept
}

 

Я получаю ошибку на

var res = контекст.SaveChangesAsync().Результат;

 Win32Exception: The wait operation timed out
 

Я попытался продлить как время ожидания подключения, так и время ожидания команды, но для появления одной и той же ошибки требуется больше времени.

Я также попытался создать новое соединение sql и команду в перехватчике, но получил ту же ошибку.

Есть ли способ сохранить данные в базу данных в перехватчике команд EF, когда он перехватывает команду изнутри транзакции?

Если нет, то есть ли способ для перехватчика игнорировать команды, находящиеся внутри транзакций?

Ответ №1:

В общем случае для ведения журнала лучше использовать отдельную структуру ведения журнала, такую как log4net, NLog или Serilog, поскольку эти регистраторы регистрируются в своих собственных потоках и должны быть нечувствительны к внешним транзакциям.

Быстрый тест с использованием Serilog в .Net core 5 показывает , что он записывается в журнал даже при вызове внутри TransactionScope , с включенным асинхронным потоком, который откатывается:

 using Serilog;
using Serilog.Events;
using Serilog.Sinks.MSSqlServer;
using System.Transactions;

using var ts = new TransactionScope(asyncFlowOption: TransactionScopeAsyncFlowOption.Enabled);

var logger = new LoggerConfiguration()
    .WriteTo
    .MSSqlServer(
        connectionString: @"Server=.sqlserver;Database=Test;Integrated Security=SSPI;",
        sinkOptions: new MSSqlServerSinkOptions { TableName = "Logs" })
    .CreateLogger();
logger.Information("info");

ts.Dispose(); // Rolls back. Added for clarity. Dispose happens anyway.
 

Каждый запуск аккуратно записывает запись журнала в тестовую базу данных.

Вот Logs стандартная таблица регистрации, которую ожидает Serilog, когда больше ничего не настроено:

 CREATE TABLE [Logs] (

   [Id] int IDENTITY(1,1) NOT NULL,
   [Message] nvarchar(max) NULL,
   [MessageTemplate] nvarchar(max) NULL,
   [Level] nvarchar(128) NULL,
   [TimeStamp] datetime NOT NULL,
   [Exception] nvarchar(max) NULL,
   [Properties] nvarchar(max) NULL

   CONSTRAINT [PK_Logs] PRIMARY KEY CLUSTERED ([Id] ASC) 
);
 

Обратите внимание, что Serilog рекомендует использовать статический объект регистратора.