ExecuteNonQuery() и SET NOCOUNT ON

KellyLynch
Дата: 16.04.2015 18:06:19
У меня есть:
MS SQL Server 2008 R2;
C# приложение – его клиент

Я встретил следующий (причём невоспроизводимый регулярно) баг. Такой вот код на клиенте приносит “количество затронутых строк” большее чем их должно быть на самом деле:

…
DbCommand cmd = db.GetStoredProcCommand("[b]TheStoredProc[/b]");
int numberOfAffectedRows = db.ExecuteNonQuery(cmd);
…
Сама процедура TheStoredProc в базе выглядит примерно так:

PROCEDURE TheStoredProc 
as

	declare @Var1 int
	
	[color=blue]SET NOCOUNT ON;[/color]

....................

[b]insert into [Workflows][/b]
	(
		WorkflowInstanceID,
		Status
	)
	select WorkflowInstanceID, 1
		from #tmp
		

	DROP TABLE #tmp


Процедуру писал не я; но из контекста следует что Клиент ожидает что в результате вызова ExecuteNonQuery ему вернётся в numberOfAffectedRows количество строк вставленных процедурой вот в этом операторе INSERT – “insert into [Workflows] …”.

Причём – только этим insert-ом. В процедуре чуть выше есть ещё один insert; но по контексту очевидно что Клиенту нужно именно и только количество строк вставленных оператором “insert into [Workflows]…”

Баг состоит в том что в результате выполнения этой процедуры кодом Клиента в его переменной numberOfAffectedRows оказалось число 464; а судя по содержимому базы, должно было быть только 419. Всё усложняется тем что баг “трудноповторимый” – его заметили раз на Production SQL Server-е; а вот повторить его на своём тестовом мне пока не удалось – возвращается правильное количество строк в numberOfAffectedRows.

Моя версия такова: виновником бага я считаю оператор “SET NOCOUNT ON” в начале процедуры TheStoredProc. Вот здесь [url=]https://social.msdn.microsoft.com/Forums/en-US/e85ad5e5-a30e-4ccf-aeab-0774185e7576/set-nocount-on-and-sqlcommandexecutenonquery-and-sqldataadapterupdate?forum=adodotnetdataproviders[/url] говорится:

автор

I have heard few side effects of using "SET NOCOUNT ON"
a. SQLCommand.ExecuteNonQuery function returning wrong number of rows if the stored procedure has SET NOCOUNT ON.



“wrong number of rows” – как раз то что у меня. Правда, на форумах в других подобных случаях я чаще всего встречал такие сообщения: “в случае использования SET NOCOUNT ON в процедуре метод ExecuteNonQuery возвращает -1”. У меня же, как видите, не -1 а “несколько большее число, чем правильное”.

Других версий у меня пока нет.

Что вы об этом думаете? Правдоподобна моя версия? Так как баг я пока ни разу не воспроизвёл в тестовой среде, мне лишь остаётся оценивать правдоподобность версий…

Исправить баг я предполагаю вставив в процедуре “SET NOCOUNT OFF” прямо перед “insert into [Workflows]…”. На тестовом сервере попробовал – всё выходит правильно. Но и (как я сказал) старый вариант на тестовом сервере тоже работает правильно…
Glory
Дата: 17.04.2015 08:19:10
KellyLynch
I have heard few side effects of using "SET NOCOUNT ON"

Ага. ОБС - одна бабка сказала.
Ниже цитируют офф.хелп
SET NOCOUNT ON prevents the sending of DONE_IN_PROC messages to the client for each statement in a stored procedure.
Что такое DONE_IN_PROC вы можете узнать здесь https://msdn.microsoft.com/en-us/library/dd340553.aspx
KellyLynch
Дата: 17.04.2015 10:32:07
Glory
KellyLynch
I have heard few side effects of using "SET NOCOUNT ON"

Ага. ОБС - одна бабка сказала.
Ниже цитируют офф.хелп
SET NOCOUNT ON prevents the sending of DONE_IN_PROC messages to the client for each statement in a stored procedure.
Что такое DONE_IN_PROC вы можете узнать здесь https://msdn.microsoft.com/en-us/library/dd340553.aspx


Исходя из этого Вы считаете невозможным описанный выше сценарий? Когда включение SET NOCOUNT ON привело к тому что ExecuteNonQuery() стал возвращать больше строк чем должен?
Если почитать официальное описание DONE_IN_PROC, то получается: установка в процедуре SET NOCOUNT ON может привести к тому что ExecuteNonQuery() возвратит меньше строк чем должен. В моём же случае он возвратил больше строк чем должен...

Согласны с моими рассуждениями? Или я что-то упустил?
Glory
Дата: 17.04.2015 10:37:23
KellyLynch
Когда включение SET NOCOUNT ON привело к тому что ExecuteNonQuery() стал возвращать больше строк чем должен?

Должно кому ? Где еще, кроме вашего неизвестного клиентского кода, проверялось что должно возвращаться и что возвращается ?

KellyLynch
Если почитать официальное описание DONE_IN_PROC, то получается: установка в процедуре SET NOCOUNT ON может привести к тому что ExecuteNonQuery() возвратит меньше строк чем должен. В моём же случае он возвратил больше строк чем должен...

А где написано, что скрывется за вашим многоточием между SET NOCOUNT ON и insert into [Workflows] ?
felix_ff
Дата: 17.04.2015 13:37:27
Glory
KellyLynch
Когда включение SET NOCOUNT ON привело к тому что ExecuteNonQuery() стал возвращать больше строк чем должен?

Должно кому ? Где еще, кроме вашего неизвестного клиентского кода, проверялось что должно возвращаться и что возвращается ?

KellyLynch
Если почитать официальное описание DONE_IN_PROC, то получается: установка в процедуре SET NOCOUNT ON может привести к тому что ExecuteNonQuery() возвратит меньше строк чем должен. В моём же случае он возвратил больше строк чем должен...

А где написано, что скрывется за вашим многоточием между SET NOCOUNT ON и insert into [Workflows] ?


PROCEDURE TheStoredProc 
   @rows INT = NULL OUTPUT
as

	declare @Var1 int
	
	[color=blue]SET NOCOUNT ON;[/color]

....................

[b]insert into [Workflows][/b]
	(
		WorkflowInstanceID,
		Status
	)
	select WorkflowInstanceID, 1
		from #tmp
		
     SET @rows = @@ROWCOUNT
	DROP TABLE #tmp


DbCommand cmd = db.GetStoredProcCommand("[b]TheStoredProc[/b]");
db.ExecuteNonQuery(cmd);
int numberOfAffectedRows = cmd.Parameters("@rows").Value;

ну а если так?
invm
Дата: 17.04.2015 13:44:58
KellyLynch
Я встретил следующий (причём невоспроизводимый регулярно) баг.
Знакомая история: получив результат, отличный от ожидаемого, разработчик сразу считает это багом продукта, а не свой ошибкой :)

1. В режиме nocount on ExecuteNonQuery будет возвращать -1. Если это не так, значит часть DML в пакете выполнялись таки с nocount off;
2. Внимательно прочитав статью документации по ExecuteNonQuery, можно понять, что использовать результат этой функции для определения количества обработанных строк можно только в простейших случаях, да и то впоследствии можно получить сюрпризы.