OptimisticConcurrencyException SQL 2008 R2 вместо вставки триггера с Entity Framework

Использование базы данных ноябрьского выпуска SQL 2008 R2 и приложения рабочей роли Azure .net 4.0 Beta 2. Рабочая роль собирает данные и вставляет их в одну таблицу SQL с одним столбцом идентификации. Поскольку, скорее всего, будет запущено несколько экземпляров этой рабочей роли, я создал триггер «Вставить вместо» в таблице SQL. Триггер выполняет функцию Upsert с помощью функции слияния SQL. Используя 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?

Спасибо! -Павел


person PaulWaldman    schedule 27.11.2009    source источник


Ответы (2)


Как вы подозревали, проблема заключается в том, что за любыми вставками в таблицу со столбцом Identity сразу же следует выбор scope_identity() для заполнения связанного значения в Entity Framework. Вместо триггера этот второй шаг пропускается, что приводит к ошибке вставки 0 строк.

Я нашел ответ в этот поток StackOverflow, в котором предлагалось добавить следующую строку в конец вашего триггера (в случае, когда элемент не соответствует и выполняется вставка).

select [Id] from [dbo].[TableXXX] where @@ROWCOUNT > 0 and [Id] = scope_identity() 

Я протестировал это с помощью Entity Framework 4.1, и это решило проблему для меня. Я скопировал сюда все свое создание триггера для полноты картины. С помощью этого определения триггера я смог добавить строки в таблицу, добавив объекты Address в контекст и сохранив их с помощью 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
person Ryan Gross    schedule 10.06.2011
comment
И если вы выполняете INSTEAD OF UPDATE, просто убедитесь, что вы установили NOCOUNT OFF прямо перед оператором INSERT и не делали никакого выбора после него. - person Tom Halladay; 25.04.2012

У меня был почти точно такой же сценарий: вставки на основе Entity Framework в представление с триггером INSTEAD OF INSERT приводили к исключению "... непредвиденное количество строк (0)...". Благодаря ответу Райана Гросса я исправил это, добавив

SELECT SCOPE_IDENTITY() AS CentrePersonID;

в конце моего триггера, где CentrePersonID — это имя ключевого поля базовой таблицы с автоматически увеличивающимся идентификатором. Таким образом, EF может обнаружить идентификатор вновь вставленной записи.

person Richard Welsh    schedule 12.12.2013
comment
Я только что узнал, что, даже если имена полей не чувствительны к регистру на стороне БД, имя поля, как закодировано выше, является чувствительным к регистру и должно точно соответствовать эквивалентному ключевому свойству на стороне кода. . - person Richard Welsh; 02.01.2014