Как использовать транзакцию при чтении данных с Entity Framework 4? - PullRequest
0 голосов
/ 10 ноября 2010

Я пытаюсь использовать уровень изоляции транзакции 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, но я бы хотел этого избежать, если это возможно.

Sidenote: Люди имеют разные мнения на разных уровнях изоляции, но этот вопрос не для обсуждения того, является ли уровень изоляции SNAPSHOT подходящим в этом случае. SNAPSHOT прекрасно работает с нашими бизнес-требованиями для этой задачи. Вопрос может действительно касаться любого другого уровня изоляции, так как другие уровни изоляции также не работают с этим кодом.

Ответы [ 2 ]

6 голосов
/ 11 ноября 2010

Извините, я трачу ваше время. Код, который я разместил, на самом деле работает, к моему удивлению. Я протестировал свою программу, используя SQL Profiler, и искал оператор «BEGIN TRANSACTION» и «SNAPSHOT УРОВЕНЬ ИЗОЛЯЦИИ SET TRANSACTION». Оказывается, однако, что для отслеживания транзакций вам необходимо специально выбрать их в списке событий в SQL Profiler. Я не знал об этом. Я думал, что транзакции будут отслеживаться как обычные команды SQL в Profiler. Кроме того, я обнаружил, что SQL Profiler не может отслеживать изменения уровней изоляции транзакций. Чтобы узнать, на каком уровне изоляции транзакции находится транзакция, вы должны запросить системное представление sys.dm_exec_sessions. Он имеет столбец с именем «action_isolation_level », числовое значение которого соответствует уровню изоляции. Вы можете увидеть, что означает число в документации для представления .

Когда я понял это, я попробовал свой оригинальный код и запросил представление, и вот! Это был действительно уровень изоляции SNAPSHOT.

Надеюсь, это может спасти кого-то еще. : -)

2 голосов
/ 10 ноября 2010

Используйте 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 оставит свою форму открытой и отправится на ланч, замораживая всю базу данных своей ожидающей транзакцией.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...