【问题标题】:How do I use a transaction when reading data with Entity Framework 4?使用 Entity Framework 4 读取数据时如何使用事务?
【发布时间】:2011-05-07 23:37:51
【问题描述】:

我正在尝试将 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));
        }
    }
}

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

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

我知道我可以使用原始 SQL 命令来做到这一点,但如果可能的话,我想避免这种情况。

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

【问题讨论】:

  • 如果 TransactionScopeDbTransaction 都不会以独立于 DB-Server 的方式执行此操作,那么我认为 EF 将添加此功能的可能性微乎其微,因为它直接使用上述两种方法。假设您说的是正确的,SQL 可能是 选择。

标签: sql-server entity-framework transactions snapshot transaction-isolation


【解决方案1】:

对不起,我一直在浪费你的时间。令我惊讶的是,我发布的代码实际上有效。我使用 SQL Profiler 测试了我的程序,并查找了“BEGIN TRANSACTION”语句和“SET TRANSACTION ISOLATION LEVEL SNAPSHOT”。但事实证明,要跟踪事务,您需要在 SQL Profiler 的事件列表中专门选择它们。我没有意识到这一点。我认为事务将作为 Profiler 中的普通 SQL 命令进行跟踪。此外,我发现 SQL Profiler 无法跟踪事务隔离级别的变化。要找出事务处于什么事务隔离级别,您必须查询 sys.dm_exec_sessions 系统视图。它有一个名为“transaction_isolation_level”的列,该列具有对应于隔离级别的数值。您可以在documentation for the view 中查看数字的含义。

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

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

【讨论】:

    【解决方案2】:

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

    一般规则:

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

    【讨论】:

    • 您好,感谢您的回复。但不幸的是,它并没有真正帮助我。我需要保持交易开放,这就是我的问题所在。 Forgetful Fred 应该不是问题,因为 SNAPSHOT 隔离级别不会锁定任何行或表。 (不同于 REPEATABLE READS fx)
    • 更新、插入和删除虽然 do 在事务的生命周期内保持锁定。并且快照隔离是真实的,不保留锁,但它保留版本,这是 tempdb 中消耗的真实空间。没有任何借口保持交易开放。
    • 我不同意。 :-) 你不能说保持交易开放总是错误的,我只是不同意这一点。我相信业务和/或应用程序要求可以使您处于可以,甚至是正确的做法,以改变一些一般规则。但我同意,一般来说,保持交易开放并不是一个好主意。但是,在这种情况下,我认为它没有任何问题。一旦用户进行了插入、更新或删除操作,事务就会关闭并开始一个新的事务以刷新窗口的内容。
    • 当你知道你在做什么时,几乎任何事情都是被允许的。但是考虑到在 SO 上说的 anything 很容易让一些无辜的人从谷歌搜索中偶然发现,我宁愿保持一种迟钝的观点并坚持一般规则。话虽如此,我想听听您为什么在演示过程中保持 snapshot 事务处于打开状态。在客户端缓存结果不会以更便宜的资源成本为服务器完成同样的任务吗?
    • 我明白了。我无法缓存结果的原因是因为结果可能有几十万行。如果演示文稿是静态的,则加载并缓存它是一个很好的建议。但是用户浏览此列表并更改过滤器,当过滤器更改时,我必须重新查询 SQL Server。对于用户来说,每次更改过滤器都需要大约一秒钟的时间来加载数据,这会很烦人。因此,我使用数据虚拟化。但是如果没有一致的读取,我向用户显示的列表可能会被打乱,并且行可能会出现不止一次。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多