Как использовать транзакцию при чтении данных с Entity Framework 4?
Я пытаюсь использовать уровень изоляции транзакции SNAPSHOT в Microsoft SQL Server 2008 R2 с Entity Framework 4.0. Тем не менее, это не так просто, как я думал.
Чтобы использовать уровень изоляции SNAPSHOT, он должен быть включен в базе данных. Я сделал это И я с помощью SQL Management Studio проверил, что уровень изоляции SNAPSHOT работает, как и ожидалось, в моей базе данных. Я хочу использовать этот уровень изоляции, потому что я хочу последовательное чтение без блокировки строк или всей таблицы. Итак, моя база данных готова для использования уровня изоляции SNAPSHOT. Все идет нормально.
В моем приложении repro, которое является приложением WPF, у меня есть окно, в которое я загружаю некоторые данные из одной таблицы. Я загружаю 5 строк одновременно каждый раз, когда нажимаю кнопку. Это XAML для окна:
<Window x:Class="EFSnapshotTransactionTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Name="UC" Closing="UC_Closing">
<DockPanel>
<Button Click="Button_Click" DockPanel.Dock="Top">Load next 5</Button>
<ScrollViewer>
<ListView ItemsSource="{Binding ElementName=UC, Path=ViewModel.Items}">
<ListView.View>
<GridView>
<GridViewColumn Header="Id" DisplayMemberBinding="{Binding Id}"/>
<GridViewColumn Header="Date" DisplayMemberBinding="{Binding Date}"/>
<GridViewColumn Header="DocumentNumber" DisplayMemberBinding="{Binding DocumentNumber}"/>
<GridViewColumn Header="Amount" DisplayMemberBinding="{Binding Amount}"/>
<GridViewColumn Header="Text" DisplayMemberBinding="{Binding Text}"/>
</GridView>
</ListView.View>
</ListView>
</ScrollViewer>
</DockPanel>
И это код для окна:
public partial class MainWindow : Window
{
private ViewModel _vm;
public ViewModel ViewModel
{
get { return _vm; }
}
public MainWindow()
{
_vm = new ViewModel();
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
_vm.LoadNextItems(5);
}
private void UC_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
_vm.Dispose();
}
Ничего волшебного здесь не происходит. Теперь код для модели представления, где происходит действие.
public class ViewModel : INotifyPropertyChanged, IDisposable
{
private ObservableCollection<Posting> _items;
private SentaFinancialsEntities _db;
private DbTransaction _dbTrans;
public ObservableCollection<Posting> Items
{
get { return _items; }
set
{
_items = value;
OnPropertyChanged("Items");
}
}
public ViewModel()
{
_items = new ObservableCollection<Posting>();
_db = new SentaFinancialsEntities();
_db.Connection.Open();
_dbTrans = _db.Connection.BeginTransaction(System.Data.IsolationLevel.Snapshot);
}
public void LoadNextItems(int count)
{
int startAt = _items.Count;
var dbPostings = (from b in _db.Postings
select b).OrderBy(b => b.Dato).Skip(startAt).Take(count);
foreach (var singleDbPosting in dbPostings)
{
Posting dto = new Posting(singleDbPosting);
_items.Add(dto);
}
}
public void Dispose()
{
_dbTrans.Commit();
_dbTrans.Dispose();
_db.Dispose();
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Здесь я пытаюсь открыть соединение с базой данных и оставить его открытым. Я пытаюсь начать транзакцию и запрашиваю уровень изоляции SNAPSHOT. Это позволило бы мне читать 5 строк за раз и получать строки такими, какими они были при открытии окна, даже если кто-то будет редактировать, удалять или вставлять строки, когда окно открыто. Но когда я запускаю трассировку с помощью SQL Profiler, при открытии окна или при загрузке строк транзакция не запускается, а запрошенный уровень изоляции не устанавливается. Когда окно открывается, соединение открывается, и Entity Framework устанавливает уровень изоляции транзакции READ COMMITTED, который является уровнем изоляции по умолчанию. То же самое происходит (т.е. ничего), если я использую TransactionScope вместо DbTransaction.
Итак, мой вопрос: как я могу начать транзакцию с уровнем изоляции SNAPSHOT и держать его открытым, пока мое окно открыто? Совершенно необходимо, чтобы транзакция оставалась открытой, чтобы я мог продолжать чтение данных из соединения, не читая строки, добавленные другими пользователями за это время.
Я знаю, что могу сделать это с помощью простых команд SQL, но я хотел бы избежать этого, если это возможно.
Примечание: у людей разные взгляды на разные уровни изоляции, но этот вопрос не для обсуждения того, подходит ли уровень изоляции SNAPSHOT в этом случае. SNAPSHOT прекрасно работает с нашими бизнес-требованиями для этой задачи. Вопрос может действительно касаться любого другого уровня изоляции, так как другие уровни изоляции также не работают с этим кодом.
2 ответа
Извините, я трачу ваше время. Код, который я разместил, к моему удивлению, действительно работает. Я протестировал свою программу с помощью SQL Profiler и искал оператор "BEGIN TRANSACTION" и "SNAPSHOT УРОВНЯ ИЗОЛЯЦИИ SET SETACATION". Оказывается, однако, что для отслеживания транзакций вам необходимо специально выбрать их в списке событий в SQL Profiler. Я не знал об этом. Я думал, что транзакции будут отслеживаться как обычные команды SQL в Profiler. Кроме того, я обнаружил, что SQL Profiler не может отслеживать изменения в уровнях изоляции транзакций. Чтобы выяснить, на каком уровне изоляции транзакции находится транзакция, вы должны запросить системное представление sys.dm_exec_sessions. У него есть столбец с именем "action_isolation_level ", числовое значение которого соответствует уровню изоляции. Вы можете увидеть, что означает число в документации для представления.
Когда я понял это, я попробовал свой оригинальный код и запросил представление, и вот! Это действительно был уровень изоляции SNAPSHOT.
Я надеюсь, что это может спасти кого-то еще некоторое время.:-)
Использовать TransactionOptions
для контроля уровня изоляции области системных транзакций:
var TransactionOptions to = new TransactionOptions ()
{ IsolationLevel = IsolationLevel.Snapshot};
using (TransactionScope scope = new TransactionScope(
TransactionScope.Required, to))
{
// Do the work here
...
scope.Complete ();
}
Если не указано, System.Transactions будет использовать Serializable
уровень изоляции. Вы также можете использовать уровень изоляции ReadCommitted
если вы включили read_committed_snapshot в базе данных.
Как общие правила:
- Лучше открыть соединение только на время операции и немедленно закрыть его. Пул соединений возьмет это оттуда.
- Абсолютно запрещено проводить транзакции в течение срока действия формы. Транзакция может существовать только в области видимости стека в течение определенной операции (т. Е. Для одного нажатия кнопки). В противном случае Forgetful Fred оставит свою форму открытой и отправится на ланч, замораживая всю базу данных своей ожидающей транзакцией.