如何在EF中使用分离的对象使Optimistic Concurrency失败?

时间:2012-01-19 19:34:21

标签: entity-framework concurrency

我们的项目目前正在迁移到EF(远离Stored Procs),其中一个增强功能(我们正在添加到架构中)是在用户将数据保存到数据库时使用Optimistic Concurrency(我们目前还没有此功能)。我遇到问题时遇到EF失败的问题。换句话说,当两个用户打开相同的记录时,每个用户都进行更改并尝试保存这些更改,第一个用于保存更新记录,第二个用户将收到错误消息。我创建了一个简单的例子来说明我的问题。

在数据库中,我有下表(并插入测试数据):

Create Table Work
(
 Id int identity(1,1) Primary Key
 ,UserIdAssignTo int null
 ,RowVer RowVersion not null
)
Insert Into Work(UserIdAssignTo)Values(1)

我创建了一个EF文件(.edmx)并将上面的表拖放到画布上。我更新了属性/列RowVer上的属性,如下所示:

  • RowVer Property / Column
  • 并发模式:已修复
  • Getter / Setter都是公开的
  • Nullable:False
  • 存储已生成:已计算
  • 类型:二进制

我有一个对象将检索和更新表格,如下所示:

public class Work
{
  public int Id { get; set; }
  public int? UserIdAssignTo { get; set; }
  public byte[] Version { get; set; }

  private string _conn = String.Empty;

  public  WorkData() 
  {
    _conn = GetConnectionsString();
  }

  public void GetById(int WorkID)
  {
    using (SQL context = new SQL(_conn))
    {
        Work fromDb = context.Works.FirstOrDefault(db => db.Id == WorkID);

        if (fromDb != null)
        {
            Id = fromDb.Id;
            UserIdAssignTo = fromDb.UserIdAssignTo;
            Version = fromDb.RowVer;
        }
    }
  }

  public void Update()
  {
    using (SQL context = new SQL(_conn))
    {
        Work fromDb = context.Works.FirstOrDefault(db => db.Id == Id);

        if (fromDb != null)
        {
            fromDb.UserIdAssignTo = UserIdAssignTo;
            fromDb.RowVer = Version;

            context.SaveChanges();

            UserIdAssignTo = fromDb.UserIdAssignTo;
            Version = fromDb.RowVer;
        }
    }
  }
}

我开发了一个测试用例来揭示我得到的错误:

[Test]
public void ConcurencyDataTest()
{
    WorkData first = new WorkData();
    first.GetById(1);
    WorkData second = new WorkData();
    second.GetById(1);

    first.UserIdAssignTo = null;
    first.Update();

    second.UserIdAssignTo = 1;
    second.Update();  // I should get an exception b/c the object is outdated
}

在“first”和“second”对象调用GetById(1)方法之后,它们的RowVer属性对于两个对象都是相同的(如预期的那样)。

我执行此测试时运行了SQL分析器

以下是“第一个”对象称为Update方法

exec sp_executesql N'update [dbo].[Work]
set [UserIdAssignTo] = null
where (([Id] = @0) and ([RowVer] = @1))
select [RowVer]
from [dbo].[Work]
where @@ROWCOUNT > 0 and [Id] = @0',N'@0 int,@1 binary(8)',@0=1,@1=0x00000000024E6E2

注意@ 1参数,“第一个”和“第二个”对象都应该在内存中使用它并在更新时使用它

当调用second.Update时,SQL分析器记录了这个:

exec sp_executesql N'update [dbo].[Work]
set [UserIdAssignTo] = @0
where (([Id] = @1) and ([RowVer] = @2))
select [RowVer]
from [dbo].[Work]
where @@ROWCOUNT > 0 and [Id] = @1',N'@0 int,@1 int,@2 binary(8)',@0=1,@1=1,@2=0x00000000024E6E2F

注意@ 1参数已更改为新值(在“first”更新之后),此时它应该是对象“second”(旧值为0x00000000024E6E2)所持有的旧值。我不明白它是如何改变的,我对如何通过EF正确实现第一次写并发感到困惑。

我实际得到的结果是“第二个”对象成功更新表,当它应该失败时。

编辑:这是为了模拟使用N层架构。我正在尝试使用分离的对象进行更新。

1 个答案:

答案 0 :(得分:2)

我认为这是因为在您的更新方法中,您将从上下文中再次检索该对象,该上下文将获得RowVer的当前值。由于它是计算的,我不认为将它设置回以前的版本会起作用。因此,当它更新时,它确实具有表中的RowVer的当前值。

我认为您需要将对象附加或添加到上下文中。

http://msdn.microsoft.com/en-us/library/bb896271.aspx

http://blogs.msdn.com/b/adonet/archive/2011/01/29/using-dbcontext-in-ef-feature-ctp5-part-4-add-attach-and-entity-states.aspx