#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 рекомендует использовать статический объект регистратора.