2016-06-28 3 views
0

Скажем, у меня есть следующие SQL заявления, которые я выполнения, используя ExecuteNonQuery(DbCommand) из C# в веб-приложениеВыполнить один SQL блок без прерывания

DECLARE @InsertedProductID INT -- this is passed as a parameter 
DECLARE @GroupID INT -- this is passed as a parameter 
DECLARE @total INT 
SET @total = (SELECT COUNT (*) FROM Products WHERE GroupID = @GroupID) 
UPDATE Products SET ProdName = 'Prod_'+ CAST(@total as varchar(15)) 
WHERE ProductID = @InsertedProductID 

Моя проблема заключается в том, что я хочу, чтобы гарантировать, что весь блок выполняется в один. Моя цель - всегда иметь ProdName уникальных для каждой группы. Если я оставлю все так, как есть, есть хороший шанс, что я получу повторяющиеся названия продуктов, если между @total и UPDATE произошел insert. Есть ли способ убедиться, что весь блок SQL выполняется сразу без прерывания. Будет ли exec или sp_executesql достичь этого? Моим последним средством было бы поставить lock вокруг ExecuteNonQuery(DbCommand) Но мне это не нравится, поскольку это создаст узкое место. Я не думаю, что использование транзакции sql полезно здесь, потому что я не беспокоюсь о целостности команд, я скорее беспокоюсь о параллельности команд.

ответ

3

Как правило, любой оператор DML (UPDATE/INSERT/DELETE) помещает блокировку (уровень строки/уровня таблицы) в конкретную таблицу, но если вы хотите явно гарантировать, что ваша операция не должна вмешиваться в другую исполняемую инструкцию, вам следует рассмотреть возможность размещения всего SQL блок внутри блока транзакции говоря

Begin transaction 
begin try 
DECLARE @InsertedProductID INT -- this is passed as a parameter 
DECLARE @GroupID INT -- this is passed as a parameter 
DECLARE @total INT 
SET @total = (SELECT COUNT (*) FROM Products WHERE GroupID = @GroupID) 
UPDATE Products SET ProdName = 'Prod_'+ CAST(@total as varchar(15)) WHERE ProductID = @InsertedProductID 

commit; // commits the transaction 
end try 
begin catch 
rollback; //Rolls back the transaction 
end catch 
end 

Вы должны также рассмотреть возможность сделать Transaction Isolation Level к READ COMMITTED, чтобы избежать dirty reads. Кроме того, очевидно, вы должны обернуть всю эту логику в stored procedure, а их выполнение, как adhoc SQL

3

Если у вас есть контроль над созданием ваших объектов SqlConnection, считают полагаться на блокировки базы данных с использованием Transactions и соответствующего IsolationLevel. Например, использование моментального снимка приведет к сбою второй транзакции, если отдельная транзакция коснулась данных до совершения совершения.

Что-то вроде:

var c = new SqlConnection(...); 
var tran1 = c.BeginTransaction(IsolationLevel.Snapshot); 
var tran2 = c.BeginTransaction(IsolationLevel.Snapshot); 
DoStuff(c, tran1);//Touch some database data 
tran1.Commit(); 
DoStuff(c, tran2);//Change the same data 
tran2.Commit();//Error! 
1

не так, что вы не можете просто сделать это

UPDATE Products 
SET ProdName = 'Prod_'+ CAST((SELECT COUNT (*) 
           FROM Products 
           WHERE GroupID = @GroupID) as varchar(15)) 
WHERE ProductID = @InsertedProductID 

Но для меня это странное обновление

1

Использование транзакции является правильным способом идти. Наряду с другими ответами вы также можете использовать TransactionScope. TransactionScope неявно регистрирует соединение и команды SQL в транзакции. Откат произойдет автоматически, если есть проблема, поскольку TransactionScope находится в используемом блоке.

Пример:

 try 
     { 
      using (var scope = new TransactionScope()) 
      { 
       using (var conn = new SqlConnection("your connection string")) 
       { 
        conn.Open(); 
        var cmd = new SqlCommand("your SQL here", conn); 
        cmd.ExecuteNonQuery(); 
       } 

       scope.Complete(); 
      } 
     } 
     catch (TransactionAbortedException ex) 
     { 

     } 
     catch (ApplicationException ex) 
     { 

     } 
Смежные вопросы