Prevent asynchronous read between update and insert - c#

I have a coworker working on an application who's run into a problem. He fires off a stored procedure using SqlCommand.ExecuteNonQuery. This stored procedure, in the same table, updates one row and inserts another. Meanwhile his application goes on and reads from the table. A race condition occurs where the read happens in between the update and the insert.
The data in question is records of access levels. When an access level changes it terminates (updates) the old access level and then instantiates (inserts) the new access level. Not infrequently the read will get in between the update and insert and find only terminated access levels--a bit of a problem.
What's the best solution to my coworker's problem?
I got a hold of the stored procedure he's trying to fix:
BEGIN
SELECT OBJECT_ACCESS_ID, PERSON_AUTH_LEVEL
INTO lAccessID, lExistingAccessLevel
FROM SHPAZ.SH_PAZ_OBJECT_ACCESS
WHERE
USER_ID = pUserID
AND (GRGR_ID = pGroupID OR (GRGR_ID IS NULL AND pGroupID IS NULL))
AND SYSDATE BETWEEN OBJECT_ACCESS_EFF_DATE AND OBJECT_ACCESS_END_DATE
FOR UPDATE;
-- If the new access level is the same as the existing, then do nothing.
IF lExistingAccessLevel = pLevel THEN
RETURN;
END IF;
-- Terminate the existing record.
UPDATE SHPAZ.SH_PAZ_OBJECT_ACCESS
SET OBJECT_ACCESS_END_DATE = SYSDATE
WHERE OBJECT_ACCESS_ID = lAccessID;
-- Create the new record.
SELECT CASE WHEN pGroupID IS NULL THEN 'Broker' ELSE 'Employer' END
INTO lSource
FROM DUAL;
INSERT INTO SHPAZ.SH_PAZ_OBJECT_ACCESS (USER_ID, GRGR_ID, SOURCE, PERSON_AUTH_LEVEL, OBJECT_ACCESS_EFF_DATE, OBJECT_ACCESS_END_DATE)
VALUES (pUserID, pGroupID, lSource, pLevel, SYSDATE, TO_DATE('12/31/2199', 'MM/DD/YYYY'));
COMMIT;
EXCEPTION
-- If there is no record, then just create a new one.
WHEN NO_DATA_FOUND THEN
SELECT CASE WHEN pGroupID IS NULL THEN 'Broker' ELSE 'Employer' END
INTO lSource
FROM DUAL;
INSERT INTO SHPAZ.SH_PAZ_OBJECT_ACCESS (USER_ID, GRGR_ID, SOURCE, PERSON_AUTH_LEVEL, OBJECT_ACCESS_EFF_DATE, OBJECT_ACCESS_END_DATE)
VALUES (pUserID, pGroupID, lSource, pLevel, SYSDATE, TO_DATE('12/31/2199', 'MM/DD/YYYY'));
END SHSP_SET_USER_ACCESS;

The solution is to remove the commit from inside your procedure, and have it
done after procedure returns. Let's say you create your procedure with name my_procedure:
SQL> exec my_procedure(my_in_arg, my_out_arg);
SQL> commit;
There should be no race at all when atomic functional operations are wrapped inside a transaction.

Related

T-SQL insert into sometimes fails to add to a table at random

So I have two tables, Records (Input ID = Primary Key) and TaskNotes (Input Id, TaskNote : No primary key).
There used to be a single stored procedure which would add to the record table, get the primary id that was generated, then add that ID to the TaskNotes table, along with the task notes text.
Recently, there was an issue where the sproc would run seemingly half way, with the record being added, but the task notes entry not being run.
I since split out into an AddRecord stored procedure and an AddTaskNotes stored procedure, which are being called from a C# application.
This works as similarly as before, however, at random the AddTaskNotes still wont be run.
I think the issue is a locking of the TaskNotes table.
Has anyone experienced this before and could let me know how it was resolved?
The current rate is about 1 failed tasknotes for every 400 record entries.
This is the AddRecord statement;
INSERT INTO Time.Records
( TeamID ,
UserID ,
TimeIN ,
TimeOUT
)
VALUES ( #TeamID , #UserID , #TimeIN , #TimeOUT );
return SCOPE_IDENTITY();
This is the AddTaskNotes statement;
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
INSERT INTO Time.TaskNotes ( InputID, TaskNotes )
VALUES ( #InputID, #TaskNotes );
END

How to monitor execution of long running stored procedure in Oracle DB_

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.

SQL Server Trigger - Prevent row update if column IsLocked BIT is true

SQL Server 2016 accessed by ASP.NET 4.6.2 MVC web application
I have table "Building" and a Building can have multiple "Components". For example, Building1 has Component1 and Component2 ... etc
It was requested of me to be able to lock a building. Lock means that a component can no longer be modified (CREATE/UPDATE/DELETE). Well, as you can imagine, this is a huge application and a component can be modified in 100+ places. No one can even answer the question, "Where all do I need to lock?".
My thought is to lock everywhere I can think of and then as a safety net create a SQL Trigger that prevents all modifications if the column on the Component table "IsLocked BIT" is true. Currently, the only way I know if a component is locked is if the IsLocked column equals true.
So, I say all of that for this. How do I create a SQL Server Trigger that prevents a row of data from being modified if the row being modified has column IsLocked = 1?
Edit 1
In my opinion, this is not a duplicate. Using Instead of Delete or Instead of... will not work for me. If I do the instead of ... then inside of that I will need to provide commit logic. I don't want to provide commit logic. I just want to run a check prior to insert, update, delete.
Edit 2 - Instead of Update/Delete is best choice
If instead of... is my best choice than can someone rewrite what I have using the instead of update/delete? I don't know how to do it. Please keep in mind that requests will be coming from a web app. I won't know if they are updating one column or the entire entity or what they will passing in. I know that the way I have it written that it will catch any insert/update/delete and prevent it if locked. If there is a better way then please write it and explain why it is better.
Here is the solution I came up with:
ALTER TRIGGER [dbo].[PreventLockedModification]
ON [dbo].[Component]
FOR INSERT, UPDATE, DELETE
AS
BEGIN
SET NOCOUNT ON;
--DETERMINE INSERT(I) UPDATE(U) OR DELETE(D)
----------------------------------------------------------------------------------------------------
DECLARE #action as char(1);
SET #action = 'I'; -- Set Action to Insert by default.
IF EXISTS(SELECT * FROM DELETED)
BEGIN
SET #action =
CASE
WHEN EXISTS(SELECT * FROM INSERTED) THEN 'U' -- Set Action to Updated.
ELSE 'D' -- Set Action to Deleted.
END
END
----------------------------------------------------------------------------------------------------
DECLARE #ErrorMsg nvarchar(100) = 'This row is locked and cannot be updated';
DECLARE #IsLocked bit;
DECLARE #BuildingId bigint;
DECLARE #UnitId bigint;
DECLARE #IsComplete_Building bit;
DECLARE #IsComplete_Unit bit;
----------------------------------------------------------------------------------------------------
IF #action = 'U' or #action = 'D'
BEGIN
SELECT #IsLocked = IsLocked FROM deleted;
IF #IsLocked = 1
BEGIN
RAISERROR (#ErrorMsg, 16, 1);
ROLLBACK TRANSACTION
END
END
----------------------------------------------------------------------------------------------------
ELSE IF #action = 'I'
BEGIN
SELECT #BuildingId = BuildingId FROM inserted;
SELECT #UnitId = UnitId FROM inserted;
SELECT #IsComplete_Building = IsComplete FROM Building WHERE BuildingId = #BuildingId
SELECT #IsComplete_Unit = IsComplete FROM Unit WHERE UnitId = #UnitId
IF #IsComplete_Building = 1 or #IsComplete_Unit = 1
BEGIN
RAISERROR (#ErrorMsg, 16, 1);
ROLLBACK TRANSACTION
END
END
----------------------------------------------------------------------------------------------------
END

Handling concurrent attempts on a SQL transaction (special situation)

I have 3 tables in a SQL Server 2008R2 database, that I need to fill their records right after each other so I used transaction to do this job with no problem. basically I have 2 INSERT store procedure queries in middle of a transaction to insert records in these tables as the code below;
The transaction was handled in C# SqlTransaction class at ASP.NET.
The following procedures just used in middle of the transaction.
First Table:
ALTER PROCEDURE [INSERT_RESOURCE]
#docID int,
#resTitle nvarchar(500),
#resCategory nvarchar(100),
#resType nvarchar(50),
#resLink nvarchar(MAX),
#createdBy nvarchar(50),
#createdDateTime datetime
AS
BEGIN
INSERT INTO Resource
VALUES(#resTitle, #resCategory, #resType,
#resLink, #createdBy, #createdDateTime)
END
Second Table:
CREATE PROCEDURE [INSERT_RESOURCE_DOCUMENT]
#docName nvarchar(200),
#docSize nvarchar(50),
#docType nvarchar(50),
#docPath nvarchar(MAX),
#docTitle nvarchar(100),
#uploadBy nvarchar(50),
#uploadDateTime datetime
AS
BEGIN
INSERT INTO Document
VALUES(#docName, #docSize, #docType, #docPath,
#docTitle, #uploadBy, #uploadDateTime)
INSERT INTO Resource_Document --Third table
VALUES(
(SELECT TOP 1 ResourceID FROM Resource ORDER BY ResourceID DESC),
(SELECT TOP 1 DocID FROM Document ORDER BY DocID DESC)
)
The above procedures are work fine but the possible issue could be on the third procedure, that is using the last ID of the first two tables to insert data in the third table, but because of the last INSERT statement is using the SELECT TOP 1 query it might pick up the wrong id if at the same time someone else use the same transaction to add some values into the first two tables.
so I was wondering how can I resolve the issue in this transaction ?
is there any other ways that I can used in third store-procedure to get those ids from the first two tables ?
Your problem here is scope. You want to gain the last inserted value for that user, during that transaction. Your select top 1 queries break the scope of the user and may select the last inserted value for any user.
To remain in the user scope, take advantage of SQL's scoping methods. Convert all 3 of these actions into one single stored procedure, then use the SCOPE_IDENTITY() method to get the value that was last inserted into an identity column for this session/user. This will safely guarantee that users won't get each others' inserted values.
Read more here: http://msdn.microsoft.com/en-us/library/ms190315.aspx
The third script will definitely lead to an issue when two records are added at the same time.
I think you could place an after trigger (for every insert on Resource) and an after update trigger (for every insert on Document).
or you could join the above two tables (Resource & Document) and then create a trigger which adds the data to the third table (Resource_Document)
For reference - http://msdn.microsoft.com/en-us/library/ms189799.aspx

ChangeConflictException when updating rows with LINQ-to-SQL

I have a form which contains a data grid and a save button.
When the user clicks the save button I check for new rows by checking a specific column. If its value is 0 I insert the row to database, and if the column value is not 0 then I update that row.
I can insert correctly but when updating an exception occurs:
ChangeConflictException was unhandled,1 of 6 updates failed.
I have checked the update statement and I'm sure it's correct. What is the problem, can any one help me?
int id;
for (int i = 0; i < dgvInstructores.Rows.Count - 1; i++)
{
id = int.Parse(dgvInstructores.Rows[i].Cells["ID"].Value.toString());
if (id == 0)
{
dataClass.procInsertInstructores(name, nationalNum, tel1, tel2,
address, email);
dataClass.SubmitChanges();
}
else
{
dataClass.procUpdateInstructores(id, name, nationalNum, tel1, tel2,
address, email);
dataClass.SubmitChanges();
}
}
I'm using linq to query sql server2005 database and vs2008
the stored procedure for 'procUpdateInstructores' is :
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go
ALTER proc [dbo].[procUpdateInstructores]
#ID int,
#name varchar(255),
#NationalNum varchar(25),
#tel1 varchar(15),
#tel2 varchar(15),
#address varchar(255),
#email varchar(255)
as
begin
BEGIN TRANSACTION
update dbo.Instructores
set
Name = #name , NationalNum = #NationalNum ,
tel1 = #tel1 , tel2 = #tel2 , address = #address , email = #email
where ID = #ID
IF (##ROWCOUNT > 0) AND (##ERROR = 0)
BEGIN
COMMIT TRANSACTION
END
ELSE
BEGIN
ROLLBACK TRANSACTION
END
end
In my experience, (working with .net forms and mvc with linq-to-sql) I have found that several times if the form collection contains the ID parameter of the data object then the update surely fails.
Even if the ID is the actual ID, it is still flagged as 'propertyChanged' when you bind it or update it or assign to another variable.
As such can we see the code for your stored procs? More specifically, the update proc?
The code you have posted above is fine, the exception should be coming from your stored proc.
However if you are confident that the proc is correct then perhaps look at the HTML code being used to generate the table. Some bugs might be present with respect to 0/1 on ID columns, etc.
In the absence of further information (what your SQL or C# update code looks like...) my first recommendation would be to do SubmitChanges once, outside the for loop, rather than submitting changes once per row.
It appears in this case that you are using a DataGridView (thus WinForms). I further guess that your dataClass is persisted on the form so that you loaded and bound the DataGridView from the same dataClass that you are trying to save the changes to in this example.
Assuming you are databinding the DataGridView to entities returned via LINQ to SQL, when you edit the values, you are marking the entity in question that it is needing to be updated when the next SubmitChanges is called.
In your update, you are calling dataClass.procUpdateInstructores(id, name, nationalNum, tel1, tel2, address, email); which immediately issues the stored procedure against the database, setting the new values as they have been edited. The next line is the kicker. Since your data context still thinks the object is still dirty, SubmitChanges tries to send another update statement to your database with the original values that it fetched as part of the Where clause (to check for concurrency). Since the stored proc updated those values, the Where clause can't find a matching value and thus returns a concurrency exception.
Your best bet in this case is to modify the LINQ to SQL model to use your stored procedures for updates and inserts rather than the runtime generated versions. Then in your parsing code, simply call SubmitChanges without calling procUpdateInstructores manually. If your dbml is configured correctly, it will call the stored proc rather than the dynamic update statement.
Also, FWIW, your stored proc doesn't seem to be doing anything more than the generated SQL would. Actually, LINQ to SQL would give you more functionality since you aren't doing any concurrency checking in your stored proc anyway. If you are required to use stored procs by your DBA or some security policy, you can retain them, but you may want to consider bypassing them if this is all your stored procs are doing and rely on the runtime generated SQL for updates.

Categories