I'm currently working on a stored procedure that will perform a couple of inserts into a database table and I want to test and see if it can return the total rows affected in a way I'd like it to. And I'm calling this stored procedure from my C# .NET code inside a transactionscope.
My question however, is how I can trigger a rollback after the stored procedure is executed and the rows affected is displayed on the console?
I'm not allowed to share my code, but I can give a pseudo code of it as it's quite simple:
using(TransactionScope scope){
//Run the procedure and save the return value in a variable
int rowsAffected = MyStoredProcedure();
//Print the value in the variable
Console.WriteLine(rowsAffected);
//Ideally, I want to perform the rollback here.
scope.Complete();
}
Is it enough to simple throw some sort of Exception or is there a better way to trigger a rollback?
It's not committed as long as you don't call the 'Complete'. Remove that and it will be rollbacked when you leave the using of the scope:
using(TransactionScope scope)
{
//Run the procedure and save the return value in a variable
int rowsAffected = MyStoredProcedure();
//Print the value in the variable
Console.WriteLine(rowsAffected);
//Don't call Complete() and it will be rollbacked
//scope.Complete();
}
Since you are using stored procedure. Why don't you keep the transaction in the stored procedure itself. Then you no need to worry about handling rollback in c# code
Related
I have code similar to below where I need to loop through multiple stored procedures and execute each stored procedure independently. My requirement is if any of stored procedures throws an exception, I need to roll back other stored procedure's actions as well.
We have implemented transactions in each stored procedure, it will roll back data only in that particular specific stored procedure operation.
I want to roll back other stored procedure operations as well if any of stored procedures throws an exception.
How can I do that?
var multipleProcNamesList = new List<string>();
multipleProcNamesList.Add("Usp_InsertData");
multipleProcNamesList.Add("Usp_DeleteData");
multipleProcNamesList.Add("Usp_UpdateData");
foreach (string procName in multipleProcNamesList)
{
// sending this current procedure to azure logic app for call respective stored procedure
}
I have two stored procedures and calling both of them in the same TransactionScope as follows.
The first - SPInsert() - inserts a new row into Table A
The second - SPUpdate() - updates the recently inserted row in the Table A
My question is, even though I have put a break point before the second stored procedure is getting called, I am unable to see the first stored procedure's row in the table, until the TransactionScope is completed.
Am I doing something wrong?
using (var transactionScope = new TransactionScope())
{
// Call and execute stored procedure 1
SPInsert();
// Call and execute stored procedure 2
SPUpdate();
transactionScope.Complete();
}
In detail:
I put a break point on SPUpdate, just right after the SPInsert, want to check on SQL to see whether or not row is being inserted, but when I run the query to check table, it keeps executing, never stops. It seems that table is not accessible at that moment. Then how would I check whether or not row is being inserted before second store procedure is getting called
Because you are in a transaction, by design and by default, SQL Server wont show you any uncommitted operations if you connect using a different session. This is why you cannot see uncommitted operations.
I have a stored procedure that is preparing data for staging area for further use. This procedure has several steps that each last several minutes. It is invoked from C# front end. Here is simplified procedure flow:
CREATE OR REPLACE PROCEDURE SP1
AS
BEGIN
INSERT INTO T1 SELECT ... FROM T2;
INSERT INTO P1 SELECT ... FROM P2;
INSERT INTO Q1 SELECT ... FROM Q2;
END;
It is very simple to invoke this in c# code (OracleConnection, OracleCommannd, ExecuteNonQuery...).
However, since this procedure will be executed from front-end by user, it would be very informative if he/she can monitor progress of this procedure. What I have found so far is OracleConnection.InfoMessage event and RAISE_APPLICATION_ERROR function.
I am adding
BEGIN
RAISE_APPLICATION_ERROR (-20001, 'My message text');
EXCEPTION
WHEN OTHERS THEN NULL;
END;
in my stored procedure with hope that InfoMessage event will be raised which is not. Here is InfoMessage event handler which is set during OracleConnection initialization (Connection.InfoMessage += OnInfoMessage):
private void OnInfoMessage(object sender, OracleInfoMessageEventArgs e)
{
foreach (OracleError err in e.Errors)
{
ShowSomeText(err.Message);
}
}
After removing BEGIN EXCEPTION block error is caught in C# code but InfoMessage has not been fired in this case too.
What I am doing wrong in this case?
I have used similar technique for MsSql server and it works smoothly. Do I miss some session/connection related setting?
PS
I am trying to avoid usage of another connection that might query some system objects or user log tables. This would be used as fallback scenario.
You can do it simply by creating a sequence and generating sequence value saving it in your C# application and then creating procedure which make use of autonomous transaction and calling it each time you are entering the step.
drop sequence sq;
create sequence s1;
drop table status;
create table status(id number, descriptions varchar2(100));
-- This procedure will update status in the status table
CREATE OR REPLACE PROCEDURE update_status (id_in IN Number,description_in IN varchar2,dml_type_in IN varchar2) AS
PRAGMA AUTONOMOUS_TRANSACTION;
id_1 number:= id_in;
description_1 varchar2(100):=description_in;
dml_type_1 char(1):=dml_type_in;
BEGIN
if dml_type_1='I' then
insert into status values(id_1, description_1);
elsif dml_type_1 = 'U' then
update status set descriptions=description_1 where id=id_1;
else
delete from status where id=id_1;
end if;
commit;
END;
--This is your main procedure
CREATE OR REPLACE PROCEDURE SP1 (id_in IN number)
AS
BEGIN
--step 1
update_status(id_in,'Entering Step 1','I');
dbms_lock.sleep(30);
--step 2
update_status(id_in,'Entering Step 2','U');
dbms_lock.sleep(30);
--step 3
update_status(id_in,'Entering Step 3','U');
dbms_lock.sleep(30);
update_status(id_in,NULL,'D');
END;
--Run procedure from 1st session
exec sp1(<whatever the sequence value is>);
--Check status from second
select * from status where id = <whatever the sequence value was>
If you are coding in website you can use something like ajax/jquery stuff been a while since I coded in C#
Warning: I have been wrong more than I have been right so follow basic
principal of software engineering and always test the code before
using it in production.
In my .NET code, inside a database transaction (using TransactionScope), I could include a nested block with TransactionScopeOption.Suppress, which ensures that the commands inside the nested block are committed even if the outer block rolls back.
Following is a code sample:
using (TransactionScope txnScope = new TransactionScope(TransactionScopeOption.Required))
{
db.ExecuteNonQuery(CommandType.Text, "Insert Into Business(Value) Values('Some Value')");
using (TransactionScope txnLogging = new TransactionScope(TransactionScopeOption.Suppress))
{
db.ExecuteNonQuery(CommandType.Text, "Insert Into Logging(LogMsg) Values('Log Message')");
txnLogging.Complete();
}
// Something goes wrong here. Logging is still committed
txnScope.Complete();
}
I was trying to find if this could be done in T-SQL. A few people have recommended OPENROWSET, but it doesn't look very 'elegant' to use. Besides, I think it is a bad idea to put connection information in T-SQL code.
I've used SQL Service Broker in past, but it also supports Transactional Messaging, which means message is not posted to the queue until the database transaction is committed.
My requirement: Our application stored procedures are being fired by some third party application, within an implicit transaction initiated outside stored procedure. And I want to be able to catch and log any errors (in a database table in the same database) within my stored procedures. I need to re-throw the exception to let the third party app rollback the transaction, and for it to know that the operation has failed (and thus do whatever is required in case of a failure).
You can set up a loopback linked server with the remote proc transaction Promotion option set to false and then access it in TSQL or use a CLR procedure in SQL server to create a new connection outside the transaction and do your work.
Both methods suggested in How to create an autonomous transaction in SQL Server 2008.
Both methods involve creating new connections. There is an open connect item requesting this functionality be provided natively.
Values in a table variable exist beyond a ROLLBACK.
So in the following example, all the rows that were going to be deleted can be inserted into a persisted table and queried later on thanks to a combination of OUTPUT and table variables.
-- First, create our table
CREATE TABLE [dbo].[DateTest] ([Date_Test_Id] INT IDENTITY(1, 1), [Test_Date] datetime2(3));
-- Populate it with 15,000,000 rows
-- from 1st Jan 1900 to 1st Jan 2017.
INSERT INTO [dbo].[DateTest] ([Test_Date])
SELECT
TOP (15000000)
DATEADD(DAY, 0, ABS(CHECKSUM(NEWID())) % 42734)
FROM [sys].[messages] AS [m1]
CROSS JOIN [sys].[messages] AS [m2];
BEGIN TRAN;
BEGIN TRY
DECLARE #logger TABLE ([Date_Test_Id] INT, [Test_Date] DATETIME);
-- Delete every 1000 row
DELETE FROM [dbo].[DateTest]
OUTPUT deleted.Date_Test_Id, deleted.Test_Date INTO #logger
WHERE [Date_Test_Id] % 1000 = 0;
-- Make it fail
SELECT 1/0
-- So this will never happen
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRAN
SELECT * INTO dbo.logger FROM #logger;
END CATCH;
SELECT * FROM dbo.logger;
DROP TABLE dbo.logger;
I've been playing around with using transaction in SQL server and in C#. Consider a store procedure which inserts a row into a three column table
alter proc spInsertItem
#itemId int
,#itemDescription varchar(50)
,#itemCost decimal
as
begin
if(#itemCost < 0)
begin
raiserror('cost cannot be less than 0',16,1)
end
else
begin
begin try
begin tran
insert into Items(itemid, [description],itemCost)
values (#itemid, #itemdescription,#itemCost)
commit tran
end try
begin catch
rollback tran
select ERROR_LINE()as errorLine
,ERROR_MESSAGE() as errorMessage
,ERROR_STATE() as errorState
,ERROR_PROCEDURE() as errorProcedure
,ERROR_NUMBER() as errorNumber
end catch
end
end
vs
create proc spInsertItem2
#itemid int
,#itemDescription varchar(50)
,#itemCost decimal
as
begin
insert into Items(ItemId,[Description],ItemCost)
values (#itemid, #itemDescription,#itemCost)
end
In the first example the user is notified that they are unable to enter in an item cost less than 0, and the rest is pretty self explanatory. This got me to thinking, if you're going to want to disallow a certain values you should need a check constraint, so I added the following constraint
alter table items
add constraint chkItemCost
check (ItemCost > 0)
Now the two stored procedures function the same in code, and the SQL is much shorter and in my opinion, easier to read in the second, shorter version. Granted, this is a very rudimentary example, but to me it seems that if you see the try/catch in code when you call the stored procedure, you can be sure of the database not being put in an inconsistent state. So, what am I missing that I shouldn't rely on C# to create transactions?
This is usually a design decision; where the logic of application housed. If you decide upon concentrating your business logic in the application code, for each atomic application logic which involves multiple trips to the database, you need to wrap that logic using transaction in C#.
Whereas, if you house business logic in the database with the help of SPs, you do not need transaction in C#.
A common scenario is:
You create one or more records in the database.
You do some post processing on this data in C#.
You update another table with the data you just processed .
The requirement is that if step 2 or 3 fails, step 1 (created records) should be rolled back. For this you need transactions. You may argue that you can put all the three steps in an SP and wrap it with a transaction; it should be possible and is generally a matter of preference of where you put your application logic.