【问题标题】:How can I return updates done in a stored procedure within a TransactionScope using Entity Framework?如何使用实体框架返回在 TransactionScope 内的存储过程中完成的更新?
【发布时间】:2015-04-13 11:08:58
【问题描述】:

我有一个存储过程,我需要使用 Entity Framework 4.0 作为更广泛事务范围的一部分来执行它。这是我所拥有的场景的快速演示:

private void button1_Click(object sender, EventArgs e)
{
    using (var scope = GetTransaction(IsolationLevel.Serializable))
    {
        var model = new SPUpdateTestEntities();

        var rows = model.TestTables.ToList();

        Debug.WriteLine("rows:");
        foreach (var row in rows)
        {
            Debug.WriteLine(row);
        }

        var random = new Random();

        var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        var result = new string(
            Enumerable.Repeat(chars, 8)
                        .Select(s => s[random.Next(s.Length)])
                        .ToArray());

        model.UpdateTestTable(rows[random.Next(rows.Count())].Id, result, random.Next());

        model.SaveChanges();

        var rowsAgain = model.TestTables.ToList();

        Debug.WriteLine("rowsAgain:");
        foreach (var row in rowsAgain)
        {
            Debug.WriteLine(row);
        }

        scope.Complete();
    }
}

private TransactionScope GetTransaction(IsolationLevel isolationLevel)
{
    var transactionOptions = new TransactionOptions();
    transactionOptions.IsolationLevel = isolationLevel;
    transactionOptions.Timeout = TransactionManager.MaximumTimeout;

    return new TransactionScope(TransactionScopeOption.Required, transactionOptions);
}

存储过程UpdateTestTable 的目的是在TestTable 中插入和更新记录。

ALTER PROCEDURE [dbo].[UpdateTestTable]
    @Id AS INTEGER,
    @Text as VARCHAR(255),
    @Number AS INTEGER
AS
BEGIN
    INSERT INTO TestTable(Text, Number) 
    VALUES (CONVERT(varchar(255), NEWID()), ABS(CHECKSUM(NewId())) % 14)

    UPDATE TestTable 
    SET Text = @Text, Number = @Number 
    WHERE Id = @Id
END

我期望的行为是allRowsAgain 包含在UpdateTestTable 中所做的任何修改。我得到的实际行为是返回插入的记录,但没有更新现有记录。

我已经尝试了隔离级别 Serializable(如图所示)、ReadComittedReadUncomitted,但这并没有像我希望的那样对结果产生影响。

编辑: 我添加了一些调试语句以使结果更清晰一些,这是输出:

第一次运行:

rows:
Id:2 - Number:1 - Text:Hello

rowsAgain:
Id:2 - Number:1 - Text:Hello
Id:19 - Number:1 - Text:0B22C83C-58E9-403C-96B4-FB3940E1F250

第二次运行:

rows:
Id:2 - Number:2135368409 - Text:CQCXCTAY
Id:19 - Number:1 - Text:0B22C83C-58E9-403C-96B4-FB3940E1F250

rowsAgain:
Id:2 - Number:2135368409 - Text:CQCXCTAY
Id:19 - Number:1 - Text:0B22C83C-58E9-403C-96B4-FB3940E1F250
Id:20 - Number:8 - Text:D5A6684B-D140-415F-A81B-36705915FAF6

谢谢, 蚂蚁

【问题讨论】:

  • 也许这是因为事务范围正在等待数据库更改,直到事务完成?
  • 我怀疑您可能是对的,但我希望能够在提交事务之前使用这些值。我也很好奇为什么插入的行可用但更新不可用。也许是 EF 缓存?
  • 确实很奇怪。

标签: .net sql-server entity-framework transactionscope


【解决方案1】:

实体框架会为每个请求打开一个新连接,除非您自己显式打开该连接。因此,这里在分布式事务中登记了多个连接。每个连接都会看到不同的数据视图。这是一种反模式。

此行为没有用,我已请求团队对其进行更改。 EF 应该与 TS 集成,但目前集成很弱。

在发出第一个命令之前手动打开连接。

EF 确保每一行最多有一个 .NET 对象。当你查询一行时,你总是得到现有的对象。在这里,它具有旧值。教训:不要混合使用 EF 和基于 SQL 的 DML。这样做时你需要非常小心。对旧连接使用新的 EF 上下文或使用 MergeOption。

【讨论】:

  • 感谢您的输入@usr。你能解释一下为什么会返回插入而不是更新的结果吗?我会看看手动控制连接。
  • 我真的怀疑情况是否如此。是否有交易。唯一的例外是第二条语句失败并且您没有注意到错误。确保您没有在此处误解某些内容。例如说SET Number = 0/0 来测试第二条语句是否运行并影响至少一行。
  • 我已经修改了我的代码示例以显示正在运行的确切代码。没有抛出异常来指示任何类型的失败。我添加了调试语句,在第一次运行和第二次运行后显示每个集合的内容。 (我有一个查询要在每次测试之前将表重置回其初始状态。)
  • 按照您的建议修改 SP 会导致如预期的“遇到除以零错误。\r\n语句已终止。”。提升插入上方的更新语句具有相同的原始结果(在删除除以零之后)。
  • 我应该更好地阅读您的代码。 EF 确保每一行最多有一个 .NET 对象。当你查询一行时,你总是得到现有的对象。在这里,它具有旧值。教训:不要混合使用 EF 和基于 SQL 的 DML。这样做时你需要非常小心。对旧连接使用新的 EF 上下文或使用 MergeOption。
猜你喜欢
  • 1970-01-01
  • 2012-05-07
  • 2015-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多