Как избежать исключения PlatformNotSupportedException при использовании пула соединений TransactionScope и SQL в .NET Core / .NET 5+
В веб-службе я запрашиваю базу данных SQL Server 2016. Используя .NET TransactionScope следующим образом, чтобы сохранить управление транзакциями на моем уровне обслуживания, но запросы / команды данных в моем коде уровня данных (классы «хранилища»), у нас есть несколько мест, которые следуют этому шаблону:
using (var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
bool needsInsert = await store1.Exists(request.id);
if (needsInsert) mainRowsUpdatedCount = await store2.Insert(request);
transaction.Complete();
}
Каждый из этих методов «store» следует этому шаблону (с использованием Dapper, хотя я подозреваю, что это не имеет значения):
const string query = @"SELECT ..."; // or INSERT or MERGE as the case may be
using IDbConnection connection = new SqlConnection(ConnectionString.Value);
return await connection.QueryAsync<T>(query, new { ... }); // or connection.ExecuteAsync as the case may be
Это отлично работает для большинства вызовов, но иногда я получаю следующее (хотя и довольно редко):
System.PlatformNotSupportedException: эта платформа не поддерживает распределенные транзакции.
Так может быть так, что в приведенном выше примере store1.Exists запускается, получает соединение, включает его в транзакцию, выполняет свой запрос, закрывается, а иногда до того, как store2.Insert может быть запущен, какой-то другой несвязанный поток получает такое же соединение от пул соединений, в котором уже есть открытая транзакция, пытается выполнить запрос и, таким образом, выдает исключение PlatformNotSupportedException, поскольку .NET Core (или .NET 5+) не поддерживает распределенные транзакции?
Если да, то как я могу это преодолеть, не передавая свои связи?
Если нет, что еще может вызвать это исключение?
1 ответ
Я столкнулся с той же проблемой (тоже довольно редко) и в конце концов пришел к выводу, что это, скорее всего, ошибка в базовых библиотеках .NET Core (либо SqlClient, либо Transaction).
Насколько я понимаю, существует разница между транзакцией .NET и локальной транзакцией SQL. Создавая, вы эффективно запускаете новую транзакцию .NET, которая всегда доступна через
Поэтому, если весь ваш код внутри a гарантирует, что всегда будет открыто не более одного открытого в любой момент времени , должно быть гарантировано, что одно и то же фактическое соединение SQL и, следовательно, локальная транзакция SQL будут повторно использоваться под капотом, и никаких попыток эскалации до должна быть совершена распределенная транзакция.
В вашем примере вы используете
Даже если есть другой
В общем, я по-прежнему считаю, что это какая-то ошибка, спрятанная глубоко в спагетти-коде транзакций .NET. ;) Моя проблема в том, что я встречаю исключение только в редких случаях в продакшене, я просто не могу воспроизвести его с помощью модульного теста или чего-то еще.