使用 Entity Framework 4 读取数据时如何使用事务?

发布于 2024-10-01 18:25:41 字数 4165 浏览 7 评论 0原文

我正在尝试将 Microsoft SQL Server 2008 R2 中的 SNAPSHOT 事务隔离级别与 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));
        }
    }
}

我在这里想做的是打开与数据库的连接并保持打开状态。我尝试启动事务并请求快照隔离级别。这将允许我一次读取 5 行并获取窗口打开时的行,即使有人在窗口打开时编辑、删除或插入行。但是,当我使用 SQL Profiler 运行跟踪时,窗口打开或加载行时没有启动任何事务,并且未设置我要求的隔离级别。当窗口打开时,将打开一个连接,实体框架将事务隔离级别设置为 READ COMMITTED,这是默认隔离级别。如果我使用 TransactionScope 而不是 DbTransaction,也会发生同样的情况(即什么也没有)。

所以我的问题是:如何启动具有 SNAPSHOT 隔离级别的事务并在窗口打开时保持其打开状态?事务保持打开状态是绝对必要的,这样我就可以继续从连接读取数据,而无需读取其他用户同时添加的行。

我知道我可以使用原始 SQL 命令来完成此操作,但如果可能的话我想避免这种情况。

旁注:人们对不同的隔离级别有不同的看法,但这个问题并不是为了讨论 SNAPSHOT 隔离级别在这种情况下是否合适。 SNAPSHOT 非常适合我们对此任务的业务需求。问题实际上也可能与任何其他隔离级别有关,因为其他隔离级别也不适用于此代码。

I'm trying to utilize the SNAPSHOT transaction isolation level in Microsoft SQL Server 2008 R2 with the Entity Framework 4.0. However, this doesn't seem to be as easy as I first thought.

To use SNAPSHOT isolation level, it has to be enabled in the database. I've done that. And I've tested by using SQL Management Studio that SNAPSHOT isolation level works as expected on my database. I want to use this isolation level because I want consistent reads without locking the rows or the whole table. So my database is ready for me to use SNAPSHOT isolation level. So far so good.

In my repro application, which is a WPF application, I have a window in which I load some data from a single table. I load 5 rows at a time every time I click a button. This is the XAML for the window:

<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>

And this is the code-behind for the window:

    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();
    }

Nothing magically going on here. Now for the code to the view model, which is where the action happens.

    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));
        }
    }
}

What I'm trying to do here, is to open a connection to the database and keep it open. I try to start a transaction and ask for the SNAPSHOT isolation level. This would allow me to read 5 rows at a time and get the rows as they were when the window was opened, even if someone would edit, delete or insert rows while the window is open. But when I run a trace with SQL Profiler, there's no transaction being started when the window opens or when I load rows, and the isolation level I asked for is not being set. When the window opens, a connection is opened, and Entity Framework sets the transaction isolation level to READ COMMITTED which is the default isolation level. The same happens (I.e. nothing) if I use a TransactionScope instead of a DbTransaction.

So my question is: How can I start a transaction with SNAPSHOT isolation level and keep it open for as long as my window is open? It's absolutely neccessary that the transaction is kept open so I can keep reading data from the connection, without reading rows other users has added in the mean time.

I know I can do it with raw SQL commands, but I would like to avoid that if possible.

Sidenote: People has different oppinions on the different isolation levels, but this question is not for discussing whether or not SNAPSHOT isolation level is appropiate in this case. SNAPSHOT works perfectly with our business requirement for this task. The question could really be about any other isolation level as well, as other isolation levels doesn't work either with this code.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

怪我鬧 2024-10-08 18:25:41

对不起,我浪费了你的时间。令我惊讶的是,我发布的代码实际上有效。我使用 SQL Profiler 测试了我的程序,并查找“BEGIN TRANSACTION”语句和“SET TRANSACTION ISOLATION LEVEL SNAPSHOT”。但事实证明,要跟踪事务,您需要在 SQL Profiler 的事件列表中专门选择它们。我不知道这一点。我认为事务会像 Profiler 中的普通 SQL 命令一样被跟踪。此外,我发现 SQL Profiler 无法跟踪事务隔离级别的更改。要了解事务所处的事务隔离级别,您必须查询 sys.dm_exec_sessions 系统视图。它有一个名为“transaction_isolation_level”的列,该列具有与隔离级别相对应的数值。您可以在视图文档中了解该数字的含义。

当我意识到这一点时,我尝试了原来的代码并查询了视图,结果发现!它确实处于 SNAPSHOT 隔离级别。

我希望这可以节省其他人的时间。 :-)

I'm sorry, I've been wasting your time. The code I posted actually works, to my surprise. I tested my program by using SQL Profiler and looked for a "BEGIN TRANSACTION" statement and a "SET TRANSACTION ISOLATION LEVEL SNAPSHOT". It turns out though, that to track transactions, you need to specifically select them in the event list in SQL Profiler. I wasn't aware of that. I thought transactions would be tracked as normal SQL commands in Profiler. Additionally, I found out that SQL Profiler cannot trace changes in transaction isolation levels. To find out what transaction isolation level a transaction is in, you have to query the sys.dm_exec_sessions system view. It has a column called "transaction_isolation_level" that has a numeric value that corresponds to an isolation level. You can see what the number means in the documentation for the view.

When I realized this, I tried my original code and queried the view, and behold! It was indeed in SNAPSHOT isolation level.

I hope this can save someone else some time. :-)

浸婚纱 2024-10-08 18:25:41

使用 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 将使用 Serialized 隔离级别。如果您在数据库中启用了 read_comfilled_snapshot,您还可以使用 ReadComfilled 隔离级别。

作为一般规则:

  • 最好仅在操作期间打开连接并立即关闭它。连接池将从那里获取它。
  • 绝对禁止在表单的生命周期内持有交易。在特定操作期间(即单击按钮),事务可以存在于堆栈范围内。否则,健忘的 Fred 将让他的表单保持打开状态并去吃午饭,并用他的待处理事务冻结整个数据库。

Use a TransactionOptions to control the isolation level of the system transaction scope:

var TransactionOptions to = new TransactionOptions () 
 { IsolationLevel = IsolationLevel.Snapshot};
using (TransactionScope scope = new TransactionScope(
    TransactionScope.Required, to))
{
   // Do the work here
   ...
   scope.Complete ();
}

If left unspecified, the System.Transactions will use Serializable isolation level. You can also use an isolation level of ReadCommitted if you enabled read_committed_snapshot in the database.

As general rules:

  • is better to open a connection just for the duration an operation and close it immediately. Connection pooling will take it from there.
  • is absolutely forbidden to hold a transaction for the lifetime of a form. Transaction can live only on a stack scope, for the duration of a specific operation (ie. for one button click). Otherwise Forgetful Fred will leave his form open and go to lunch, freezing the entire database with his pending transaction.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文