Краткая версия
Мы хотим отключить автоматическое присоединение к внешним транзакциям (System.Transactions.TransactionScope) для всей Entity Framework Core 8 DbContext. DbContext ни в коем случае не должен быть включен в уже действующую внешнюю транзакцию.
Мы не хотим явно создавать новый TransactionScope с TransactionScopeOption.Suppress везде в коде, где мы обращаемся к DbContext, и поэтому мы хотели бы отключить его глобально. Мы хотим абстрагировать эти детали реализации и уменьшить сложность кода. Разработчиков не должно волновать это при доступе к DbContext.
Более длинная версия с большим контекстом
У нас есть два разных ядра Entity Framework Core, которые подключаются к двум разным базам данных. Одна из двух баз данных используется только для чтения, и поэтому нам не нужны (распределенные) транзакции по обеим базам данных. Для этой базы данных мы хотели бы отключить привязку к внешним транзакциям.
У нас есть шаблон декоратора на месте создания DbContext. Внутри декорируемого объекта можно использовать обе базы данных, и по умолчанию обе TransactionScope подключаются к внешней транзакции. Мы хотим отключить регистрацию базы данных, доступной только для чтения.
Что мы пробовали
Для ADO.NET это возможно, указав свойство DbContext в строке подключения. Но, похоже, это не работает для платформы сущностей, поскольку EF не учитывает это свойство строки подключения.
Мы попытались создать Enlist=false и переопределить метод DbCommandInterceptor и установить транзакцию соединения на ReaderExecuting:
public override InterceptionResult<DbDataReader> ReaderExecuting(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result)
{
command.Connection?.EnlistTransaction(null);
return base.ReaderExecuting(command, eventData, result);
}
Это вызывает исключение, что в соединении есть транзакция, которую необходимо завершить в первую очередь.
Мы рассматривали возможность создания нового null с TransactionScope внутри перехватчика. Но мы не уверены, как правильно справиться с размещением TransactionScopeOption.Suppress внутри перехватчика. Это также кажется хакерским, и мы не хотим приводить к утечкам ресурсов из-за неправильной реализации.
Спасибо @GertArnold. К сожалению, полное удаление TransactionScope в этом случае невозможно. Мы полагаемся на него, поскольку у нас есть и другой код, который подключается к нему (вне структуры сущности).





Благодаря подсказкам в комментариях @Святослава Данилива, у меня все получилось, выполнив следующие шаги:
Создайте класс, производный от SqlServerConnection, чтобы отключить внешние транзакции на уровне Entity Framework:
internal class NoAmbientTransactionSqlServerConnection(
RelationalConnectionDependencies dependencies)
: SqlServerConnection(dependencies)
{
protected override bool SupportsAmbientTransactions => false;
}
При регистрации DbContext замените IRelationalConnection этой пользовательской реализацией, используя DbContextOptionsBuilder:
options.ReplaceService<IRelationalConnection, NoAmbientTransactionSqlServerConnection>();
Этого все еще недостаточно, поскольку базовый класс SqlServerConnection внутренне использует DbConnection из клиента SQL. Это DbConnection все равно будет включено во внешнюю транзакцию. Поэтому добавьте Enlist=false в строку подключения, чтобы отключить привязку и на уровне клиента SQL.
Небольшим недостатком является то, что SqlServerConnection — это внутренний API Entity Framework Core, который выдает предупреждение при его использовании, и его следует использовать с осторожностью, и его можно изменить или удалить без предварительного уведомления.
Возможно (просто вопрос), вам следует пересмотреть, нужна ли вам эта область транзакций и зачем вам это нужно. Я видел множество случаев, когда транзакции/области транзакций запускались, но при более внимательном рассмотрении оказалось, что код можно переписать так, чтобы он выполнял только один вызов SaveChanges.