Microsoft SQL Server2008R2のSNAPSHOTトランザクション分離レベルをEntityFramework4.0で利用しようとしています。しかし、これは私が最初に思ったほど簡単ではないようです。
SNAPSHOT分離レベルを使用するには、データベースで有効にする必要があります。私はそれをしました。また、SQL Management Studioを使用して、SNAPSHOT分離レベルがデータベースで期待どおりに機能することをテストしました。行またはテーブル全体をロックせずに一貫した読み取りが必要なため、この分離レベルを使用したいと思います。これで、データベースでSNAPSHOT分離レベルを使用できるようになりました。ここまでは順調ですね。
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を使用してトレースを実行すると、ウィンドウが開いたとき、または行をロードしたときにトランザクションが開始されず、要求した分離レベルが設定されていません。ウィンドウが開くと、接続が開かれ、EntityFrameworkはトランザクション分離レベルをデフォルトの分離レベルであるREADCOMMITTEDに設定します。DbTransactionの代わりにTransactionScopeを使用した場合も、同じことが起こります(つまり何も起こりません)。
だから私の質問は:SNAPSHOT分離レベルでトランザクションを開始し、ウィンドウが開いている限りそれを開いたままにするにはどうすればよいですか?他のユーザーが追加した行を読み取らずに、接続からデータを読み取り続けることができるように、トランザクションを開いたままにしておくことが絶対に必要です。
生のSQLコマンドでそれができることは知っていますが、可能であればそれを避けたいと思います。
補足:人々はさまざまな分離レベルでさまざまな意見を持っていますが、この質問は、この場合にSNAPSHOT分離レベルが適切であるかどうかを議論するためのものではありません。SNAPSHOTは、このタスクのビジネス要件と完全に連携します。他の分離レベルもこのコードでは機能しないため、問題は他の分離レベルについても同様である可能性があります。