乐观并发异常 -- 使用Entity Framework时SQL 2008 R2的插入触发器

9
使用 SQL 2008 R2 November 发布版数据库和 .net 4.0 Beta 2 Azure 工作角色应用程序。工作角色收集数据并将其插入一个具有一个标识列的单个 SQL 表中。由于可能会运行多个此工作角色实例,因此我在 SQL 表上创建了一个 Insert Instead Of 触发器。触发器使用 SQL Merge 函数执行 Upsert 功能。使用 T-SQL,我能够验证插入而不是触发器的功能正常,新行被插入而现有行被更新。 这是我的触发器代码:
Create Trigger [dbo].[trgInsteadOfInsert] on [dbo].[Cars] Instead of Insert
as
begin
set nocount On

merge into Cars as Target
using inserted as Source
on Target.id=Source.id AND target.Manufactureid=source.Manufactureid
when matched then 
update set Target.Model=Source.Model,
Target.NumDoors = Source.NumDoors,
Target.Description = Source.Description,
Target.LastUpdateTime = Source.LastUpdateTime,  
Target.EngineSize = Source.EngineSize
when not matched then
INSERT     ([Manufactureid]
       ,[Model]
       ,[NumDoors]
       ,[Description]
       ,[ID]
       ,[LastUpdateTime]
       ,[EngineSize])
 VALUES
       (Source.Manufactureid,
       Source.Model,
       Source.NumDoors,
       Source.Description,
       Source.ID,
       Source.LastUpdateTime,
       Source.EngineSize);
  End

在工作角色中,我正在使用Entity Framework进行对象模型。当我调用SaveChanges方法时,我收到以下异常:

OptimisticConcurrencyException
Store update, insert, or delete statement affected an unexpected number of rows (0).    Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.

我理解这可能是由于SQL没有为每个新插入/更新的行报告IdentityScope所致。然后EF认为这些行没有被插入,事务最终未被提交。

如何处理这个异常?也许使用SQL合并功能的OUTPUT选项是最好的方法?

谢谢! -Paul

2个回答

9
正如您所怀疑的那样,问题在于对具有Identity列的表进行插入操作后,紧接着就会选择scope_identity()以填充实体框架中相关值。而触发器的代替导致第二步被忽略,从而导致“未插入任何行”错误。
我在这个StackOverflow线程中找到了一个答案,建议在触发器末尾添加以下行(在不匹配项目且执行插入的情况下)。
select [Id] from [dbo].[TableXXX] where @@ROWCOUNT > 0 and [Id] = scope_identity() 

我使用Entity Framework 4.1进行了测试,它解决了我的问题。为了完整起见,我在此处复制了整个触发器创建过程。使用这个触发器定义,我能够通过向上下文中添加地址实体并使用context.SaveChanges()保存它们来向表中添加行。

ALTER TRIGGER [dbo].[CalcGeoLoc]
   ON  [dbo].[Address]
   INSTEAD OF INSERT
AS 
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT OFF;

-- Insert statements for trigger here
INSERT INTO Address (Street, Street2, City, StateProvince, PostalCode, Latitude, Longitude, GeoLoc, Name)
SELECT Street, Street2, City, StateProvince, PostalCode, Latitude, Longitude, geography::Point(Latitude, Longitude, 4326), Name 
FROM Inserted;

select AddressId from [dbo].Address where @@ROWCOUNT > 0 and AddressId = scope_identity();
END

1
如果你正在进行INSTEAD OF UPDATE操作,请确保在INSERT语句之前设置"SET NOCOUNT OFF",并且不要在其后执行任何类型的select操作。 - Tom Halladay

3

我几乎遇到了完全相同的情况:使用Entity Framework向一个带有 INSTEAD OF INSERT 触发器的视图执行插入操作,结果会导致出现“……意外的行数(0)……”异常。由于Ryan Gross的回答,我通过添加以下内容解决了这个问题:

SELECT SCOPE_IDENTITY() AS CentrePersonID;

在我的触发器结束时,CentrePersonID 是底层表的关键字段的名称,该字段具有自动增量标识。这样,EF可以发现新插入记录的ID。

我刚刚发现,即使在数据库端字段名称不区分大小写,上面编码的字段名称仍然区分大小写,并且必须与代码端的等效关键属性完全匹配。 - Richard Welsh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接