Ответ 1
Хорошо, после некоторого копания сегодня я немного об этом узнал, что я поделюсь результатами как для других, так и для того, чтобы узнать мнения и предложения.
Существует несколько причин, по которым моя проблема зависит от среды.
Версия сервера базы данных:
Прежде всего, результат операций зависит от версии SQL Server, которую вы используете (протестирован на SQL Server 2012 и SQL Server 2014).
SQL Server 2012
В SQL Server 2012 последний уровень изоляции будет следовать за соединением при последующих операциях, даже если он будет выпущен обратно в пул соединений и возвращен обратно из других потоков/действий. На практике; это означает, что если вы в каком-либо потоке/действии устанавливаете уровень изоляции для чтения без фиксации с использованием транзакции, соединение сохраняет это, пока другая область транзакций не установит его на другой уровень изоляции (или выполнив команду SET TRANSACTION ISOLATION LEVEL на подключение). Нехорошо, вы можете внезапно получить грязные чтения, не зная об этом.
Например:
Console.WriteLine(context.MatchTypes.Where(mt => mt.Id == 2).Select(mt => mt.LastUpdated).First());
using (var scope = new TransactionScope(TransactionScopeOption.Required,
new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadUncommitted
}))
{
Console.WriteLine(context.MatchTypes.Where(mt => mt.Id == 2)
.Select(mt => mt.LastUpdated).First());
scope.Complete(); //tested both with and without
}
Console.WriteLine(context.MatchTypes.Where(mt => mt.Id == 2).Select(mt => mt.LastUpdated).First());
В этом примере первая команда EF будет запускаться с базой данных по умолчанию, а внутри области транзакций будет работать с ReadUncommitted, а третья - с ReadUncommitted.
SQL Server 2014
В SQL Server 2014, с другой стороны, каждый раз, когда соединение получается из пула соединений, процедура sp_reset_connection (похоже, что это так) в любом случае устанавливает уровень изоляции по умолчанию в базе данных, ДАЖЕ, если соединение извлекается из одной и той же области транзакции. На практике; это означает, что если у вас есть область транзакций, в которой вы выполняете две последующие команды, только первый получит уровень изоляции области транзакции. Также не хорошо; вы получите (на основе уровня изоляции по умолчанию в базе данных) либо получите данные о блокировке или моментальном снимке.
Например:
Console.WriteLine(context.MatchTypes.Where(mt => mt.Id == 2).Select(mt => mt.LastUpdated).First());
using (var scope = new TransactionScope(TransactionScopeOption.Required,
new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadUncommitted
}))
{
Console.WriteLine(context.MatchTypes.Where(mt => mt.Id == 2)
.Select(mt => mt.LastUpdated).First());
Console.WriteLine(context.MatchTypes.Where(mt => mt.Id == 2)
.Select(mt => mt.LastUpdated).First());
scope.Complete();
}
В этом примере первая команда EF будет запускаться с базой данных по умолчанию, первая из транзакций будет выполняться с помощью ReadUncommitted, а вторая в рамках области будет снова запущена по умолчанию для базы данных.
Проблема с открытием вручную:
Существуют и другие проблемы, которые возникают в разных версиях SQL Server с открытым вручную соединением, однако нам строго не нужно это делать, поэтому я не собираюсь сейчас останавливаться на этой проблеме.
Использование Database.BeginTransaction:
По какой-то причине логика Database.BeginTransaction для Entity Framework, похоже, работает в обеих базах данных, которая в порядке, но в нашем коде мы работаем с двумя разными базами данных, а затем нам нужны области транзакций.
Вывод:
Я обнаружил, что эта обработка уровня изоляции в сочетании с областями транзакций в SQL Server довольно затруднительна после этого, на мой взгляд, небезопасно использовать и может вызвать серьезные проблемы в любом приложении, как я его вижу. Будьте очень осторожны с этим.
Но факт остается фактом, нам нужно, чтобы это работало в нашем коде. Имея дело с утомительной поддержкой в MS в последнее время не с тем большим результатом, я сначала найду обходное решение, которое работает для нас. Затем я расскажу о своих выводах с помощью Connect и надеюсь, что Microsoft сделает некоторые действия в области обработки транзакций и соединений.
Решение:
Решение (насколько я пришел) выглядит следующим образом.
Вот требования, которые это решение будет иметь: 1. База данных ДОЛЖНА должна быть ЗАВЕРШЕНА на уровне изоляции из-за других приложений, которые работают с той же базой данных, которая требует этого, мы не можем использовать значения READ COMMITTED SNAPSHOT по умолчанию в базе данных 2. Наше приложение MUST имеет значение по умолчанию для уровня изоляции SNAPSHOT - Это можно решить, используя SET TRANSACTION ISOLATIONLEVEL SNAPSHOT 3. Если есть область транзакций, нам нужно соблюдать уровень изоляции для этого
Таким образом, основываясь на этих критериях, решение будет таким:
В конструкторе контекста я регистрируюсь в событии StateChange, где я в свою очередь, когда состояние изменено на Open, и нет активной транзакции по умолчанию для уровня изоляции для моментального снимка с использованием классического ADO.NET. Если используется область транзакции, мы должны соблюдать ее настройки, запустив SET TRANSACTION ISOLATIONLEVEL на основе настроек здесь (чтобы ограничить наш собственный код, мы разрешим только IsolationLevel ReadCommitted, ReadUncommitted и Snapshot). Что касается транзакций, созданных Database.BeginTransaction в контексте, кажется, что это соблюдается, так как это так, мы не делаем никаких специальных действий с этими типами транзакций.
Вот код в контексте:
public MyContext()
{
Database.Connection.StateChange += OnStateChange;
}
protected override void Dispose(bool disposing)
{
if(!_disposed)
{
Database.Connection.StateChange -= OnStateChange;
}
base.Dispose(disposing);
}
private void OnStateChange(object sender, StateChangeEventArgs args)
{
if (args.CurrentState == ConnectionState.Open && args.OriginalState != ConnectionState.Open)
{
using (var command = Database.Connection.CreateCommand())
{
if (Transaction.Current == null)
{
command.CommandText = "SET TRANSACTION ISOLATION LEVEL SNAPSHOT";
}
else
{
switch (Transaction.Current.IsolationLevel)
{
case IsolationLevel.ReadCommitted:
command.CommandText = "SET TRANSACTION ISOLATION LEVEL READ COMMITTED";
break;
case IsolationLevel.ReadUncommitted:
command.CommandText = "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED";
break;
case IsolationLevel.Snapshot:
command.CommandText = "SET TRANSACTION ISOLATION LEVEL SNAPSHOT";
break;
default:
throw new ArgumentOutOfRangeException();
}
}
command.ExecuteNonQuery();
}
}
}
Я тестировал этот код как в SQL Server 2012, так и в 2014 году, и, похоже, он работает. Это не самый приятный код, и он имеет свои ограничения (например, для каждого запуска EF всегда выполняется SET TRANSACTION ISOLATIONLEVEL по отношению к базе данных и, таким образом, добавляется дополнительный сетевой трафик.)