EF并发性导致触发器出现问题

时间:2013-04-10 15:38:10

标签: asp.net-mvc-4 entity-framework-4.1

在我的SQL表(2008)中,我有一个名为Timestamp的时间戳列。

在我的.Net项目中,我有一个带有属性的POCO类

public byte[] Timestamp {get; set;}

在我的配置代码中,我有以下内容:

Property(p => p.Timestamp).IsRowVersion();

现在,如果我打开两个编辑屏幕进行一次更改并保存(保存被接受),然后在第二次进行更改并保存(使用DbUpdateConcurrencyException拒绝保存)。

我发现奇怪的一件事是,即使我收到并发异常,SQL事件探查器也会显示发送到SQL数据库的更新请求。更新未在数据库上提交,但已发送。这是正常的吗?我希望EF提前检查,甚至不发送请求。

最后,如果我在此表上启用触发器:

ALTER TRIGGER [dbo].[trg_person_log_changes]
   ON  [dbo].[Person]
   AFTER INSERT,DELETE,UPDATE
AS 
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE @auditBody XML
DECLARE @actionType char(1)
DECLARE @username nvarchar(100)

if not exists(select * from deleted)
    Begin
        SET @actionType = 'I'
        SET @username = (select Case 
                                    When lastchangedby is null or lastchangedby = '' then Suser_name()
                                    Else lastchangedby
                                End  
                         from inserted)
    End
else if not exists(select * from inserted)
    Begin
        SET @actionType = 'D'
        SET @username = Suser_name()
    End
else
    Begin
        SET @actionType = 'U'
        SET @username = (select distinct Case 
                                    When lastchangedby is null or lastchangedby = '' then Suser_name()
                                    Else lastchangedby
                                End  
                         from inserted)
    End

If @actionType = 'I'
    Set @auditBody = (select 'Person' as "@tableName",  'True' as "@synch",
      (select * from inserted for xml path('DataItem'), type, binary base64)
      for xml path('Root'))
Else
    SET @auditBody = (select 'Person' as "@tableName", 'True' as "@synch",
      (select * from deleted for xml path('DataItem'), type, binary base64)
      for xml path('Root'))

insert into [dbo].[AuditLog]
           ([Action]
           ,[ActionDate]
           ,[ActionUser]
           ,[AuditData]
           )
    values (
           @actionType
           ,getdate()
           ,@username
           ,@auditBody)
END

现在,当我尝试保存第二个编辑时,我不再获得DbUpdateConcurrencyException,我收到一条错误,指出ActionUser不能为null。再一次,我认为我的触发器正在执行,因为即使存在冲突,EF也允许更新通过。

关于我可能做错的任何想法?

注意

这是一个MVC项目。在我的Edit POST控制器中,我收到一个包含所有表单属性的DTO对象(其中一个是Timestamp - 我编辑表单上的隐藏字段)。我正在从datacontext加载已编辑的人员,并将编辑后的属性从DTO对象映射到从datacontext返回的Domain对象。

1 个答案:

答案 0 :(得分:2)

正确执行乐观并发的唯一方法是进行版本检查,并且100%确定在实际更新完成之前没有任何变化。最方便的方法是在UPDATE语句中加入版本检查,这就是EF所做的。所以它会发送一个更新语句,看起来像

UPDATE table SET columnA = value
WHERE rowversion = xxxx

但是,当语句没有找到内存中具有rowversion的行时,它会返回与预期不同的受影响行数,并且抛出异常。 交易回滚。所以是的,你在SQL事件探查器中看到了一个声明,但它永远不会被提交。

触发器作为更新语句的一部分运行,即在报告有关更新命令的EF之前。显然,@username在进程中没有得到值,因此抛出一个SQL异常,在发现任何并发冲突之前破坏更新方。