I'm working on a real estate windows forms application on C# and SQL Server, which is also a multi-user app. I'm not using Entity Framework or LINQ in it!
I'm struggling on how to properly handle the optmistic concurrency violation. Assume there are two tables in DB, Properties and Addresses.
Properties contain general property details such as type of
property, no of rooms, price etc.
Addresses has the property addresses.
When my form loads, it loads all the information of properties to a datagridview. In this case it load all information in Properties and Addresses table to a datagridview.
When the user clicks a row on datagridview it loads the data on that row to an editing form. Then user updates and updates the db. In this situation an optimistic concurrency violation (OCV) can occur.
But How do I store the rowversion value of data that was fetched from Properties and Addresses in such a way that will help to handle this OCV violation?
Initially I was thinking about when the user double clicks on the row, the rowversions for that record in the Properties and Addresses tables can be stored in a transaction table. But after the records been loaded their rowversion values can be changed by another user thus whats displayed on the datagridview might be out dated. So when the user clicks a row, i need to send the exact data on the "grid" (not in DB) to the editing form along with their original rowversion values. Whats the proper way to do this?
P.S. I was told the proper way to handle OCV is to handle it in the SQL server and not in the ADO .NET since OCV is a database operation. So i would prefer guidance to handle it in the database!
thanks
Optimistic concurrency should be handle by database. For instance
CREATE TABLE Customer (
Id int IDENTITY (1, 1) NOT NULL,
FirstName nvarchar (256) NULL,
LastName nvarchar (256) NULL,
_rowVersion rowversion NOT NULL -- Value generated by SQL Server each time the row is updated
)
When loading rows you need to get the row version generated by SQL Server
SELECT Id, FirstName, LastName, _rowVersion
FROM Customer
When saving the row, you have to add a test to ensure the RowVersion has not changed between the time you load the row and now
UPDATE Customer SET
FirstName = #FirstName,
LastName] = #LastName,
WHERE Id = #Id AND _rowVersion] = #_rowVersion -- Update using the row version
-- This part can be done in the .NET application if you prefer
SELECT #rowcount = ##ROWCOUNT
IF(#rowcount = 0) -- No row updated => Concurrency Error
BEGIN
RAISERROR ('Concurrency error in procedure %s', 16, 1, 'Customer_Save')
RETURN
END
Related
Suppose I have a student registration form with 85 fields in my c# project and there are three buttons Save, Update, Delete.
The person who update the field knows very well that which field has updated but how other person will know that which field has updated ? Because...
If I click update button without changing any value, the Update query execute and the same update query execute too when I click update button with some changes.
So I want that database should detect which field has updated in update query.
The traditional way says that store the previous values and then compare them with new values one by one field. But this slow down the performance.
Any smart way ?
You can implement optimistic concurrent using a rowversion column. This avoids the need to check the old/new values of each column individually. Specify the original rowversion in the WHERE clause of the UPDATE statement and verify a row was updated. Proc example:
CREATE TABLE dbo.OptimisticConcurrencyExample(
OptimisticConcurrencyExampleID int NOT NULL
CONSTRAINT PK_OptimisticConcurrencyExample PRIMARY KEY
, Column1 int NOT NULL
, Column2 int NOT NULL
, Column3 int NOT NULL
, RowVersion rowversion
);
GO
CREATE PROC dbo.UpdateOptimisticConcurrencyExample
#OptimisticConcurrencyExampleID int
, #Column1 int
, #Column2 int
, #Column3 int
, #OriginalRowVersion rowversion
AS
UPDATE dbo.OptimisticConcurrencyExample
SET
Column1 = #Column1
, Column2 = #Column2
, Column3 = #Column3
WHERE
OptimisticConcurrencyExampleID = #OptimisticConcurrencyExampleID
AND RowVersion = #OriginalRowVersion;
IF ##ROWCOUNT = 0
BEGIN
RAISERROR('data updated or deleted by another user', 16, 1);
END
GO
When an optimistic concurrency error occurs, the app code can refresh the data with the current values, let the user know someone else updated the row, and allow the user to re-enter changes. You could get fancy and transparently merge pending changes in code (checking old/new values for each column) and notifying the user only if a conflict occurs. Similarly, you could present the user with a merge form after a conflict with the entered and current values side-by-side and the different values marked, etc.
But given conflicts are typically rare (unlikely that different users will update the same student record at the same time), the additional development effort may not be worth the trouble.
I have one way to do that thing using table versioning using that you can maintain every update, create, delete the record.
Tutorial for table versioning
Another Tutorial
The second way is you have to add audit table or field that store SQL query and also store which user change the data and also store last modified date like that.
Why do you need a save button and an update button? Same thing, right?
Modify the logic in your program to allow an update only if the user
changes the data on the student registration form relative to the
data in the database.
Read record from database
Store record data in a local DataRow or List
Load data onto the student registration form
update_button.enable = false
delete_button.enable = true
Create TextChange event methods for each TextBox on the student registration form
When a TextChange event method fires, compare the TextBox text to the corresponding DataRow field or List item (case
insensitive).
If they don't match, enable the update button
If the user clicks the update button, update the record and disable the update button
I have tables Cases and Degrees with relation Cases.CID - CDegrees.CID. CID is Primary Key of Cases with AutoIncrement.
Both tables are used in one form and it's assumed that user can add new records to both tables same time and than save whole master/child data in one GUI action.
So in Dataset where I created FK I set "Both Relation and Foreign Key Constraint" to ensure during update of new record in Cases table, retrieved IDENTITY value will cause child records to update from -1 to retrieved CID value.
When I update Cases adapter, it causes to retrieve an new IDENTITY value and cascade update in CDegrees child record works ok too. But CDegrees's update causes Insert script with [CID]=-1 (original value). I changed Insert parameter #CID of CDegrees to "Proposed" version but same happens (seen in SQL Profiler).
Actually my task is mach more complicated, I just simplified task to localize my problem.
To describe more clearly.
before update
Both [Cases] and [CDegrees] have one new records with [CID]=-1
after [Cases] update both [Cases] and [CDegrees] have new CID identity value, just [CDegrees].[CID] "Current" value is -1 and "Proposed" is retrieved identity.
But when I call [CDegrees]'s dataset update, it sends Insert Command to SQL with [CID]=-1 regardless that I specified #CID parameter source as proposed value of [CID].
That is strange and funny. It appeared that calendar control on detailsform somehow causes but preventing details data cascade update according master identity value of CID field.
I just changed date field bound control from MonthCalendar to DateTimePicker and it worked.
I want to get new id(Identity) before insert it. so, use this code:
select SCOPE_IDENTITY() AS NewId from tblName
but is get this:
1- Null
2- Null
COMPUTED COLUMN VERSION
You'll have to do this on the sql server to add the column.
alter table TableName add Code as (name + cast(id as varchar(200)))
Now your result set will always have Code as the name + id value, nice because this column will remain updated with that expression even if the field are changed (such as name).
Entity Framework Option (Less ideal)
You mentioned you are using Entity Framework. You need to concatenate the ID on a field within the same record during insert. There is no capacity in SQL (outside of Triggers) or Entity Framework to do what you are wanting in one step.
You need to do something like this:
var obj = new Thing{ field1= "some value", field2 = ""};
context.ThingTable.Add(obj);
context.SaveChanges();
obj.field2 = "bb" + obj.id; //after the first SaveChanges is when your id field would be populated
context.SaveChanges();
ORIGINAL Answer:
If you really must show this value to the user then the safe way to do it would be something like this:
begin tran
insert into test(test) values('this is something')
declare #pk int = scope_identity()
print #pk
You can now return the value in #pk and let the user determine if its acceptable. If it is then issue a COMMIT else issue the ROLLBACK command.
This however is not a very good design and I would think a misuse of the how identity values are generated. Also you should know if you perform a rollback, the ID that would of been used is lost and wont' be used again.
This is too verbose for a comment.
Consider how flawed this concept really is. The identity property is a running tally of the number of attempted inserts. You are wanting to return to the user the identity of a row that does not yet exist. Consider what would happen if you have values in the insert that cause it too fail. You already told the user what the identity would be but the insert failed so that identity has already been consumed. You should report to the user the value when the row actually exists, which is after the insert.
I can't understand why you want to show that identity to user before insert, I believe (as #SeanLange said) that is not custom and not useful, but if you insist I think you can do some infirm ways. One of them is
1) Insert new row then get ID with SCOPE_IDENTITY() and show to user
2) Then if you want to cancel operation delete the row and reset
identity (if necessary) with DBCC CHECKIDENT('[Table Name]', RESEED,
[Identity Seed]) method
Other way is not using the Identity column and manage id column by yourself and it must be clear this approach can't be work in concurrency scenarios.
I think perhaps you're confusing the SQL identity with a ORACLE sequence.
They work completely different.
With the ORACLE sequence you'll get the sequence before you insert the record.
With a SQL Identity, the last identity generated AFTER the insert in available via the SCOPE_IDENTITY() function.
If you really need to show the ID to the user before the insert, your best bet is to keep a counter in a separate table, and read the current value, and increment that by one. As long as "gaps" in the numbers aren't a problem.
I have created two threads in C# and I am calling two separate functions in parallel. Both functions read the last ID from XYZ table and insert new record with value ID+1. Here ID column is the primary key. When I execute the both functions I am getting primary key violation error. Both function having the below query:
insert into XYZ values((SELECT max(ID)+1 from XYZ),'Name')
Seems like both functions are reading the value at a time and trying to insert with the same value.
How can I solve this problem.. ?
Let the database handle selecting the ID for you. It's obvious from your code above that what you really want is an auto-incrementing integer ID column, which the database can definitely handle doing for you. So set up your table properly and instead of your current insert statement, do this:
insert into XYZ values('Name')
If your database table is already set up I believe you can issue a statement similar to:
alter table your_table modify column you_table_id int(size) auto_increment
Finally, if none of these solutions are adequate for whatever reason (including, as you indicated in the comments section, inability to edit the table schema) then you can do as one of the other users suggested in the comments and create a synchronized method to find the next ID. You would basically just create a static method that returns an int, issue your select id statement in that static method, and use the returned result to insert your next record into the table. Since this method would not guarantee a successful insert (due to external applications ability to also insert into the same table) you would also have to catch Exceptions and retry on failure).
Set ID column to be "Identity" column. Then, you can execute your queries as:
insert into XYZ values('Name')
I think that you can't use ALTER TABLE to change column to be Identity after column is created. Use Managament Studio to set this column to be Identity. If your table has many rows, this can be a long running process, because it will actually copy your data to a new table (will perform table re-creation).
Most likely that option is disabled in your Managament Studio. In order to enable it open Tools->Options->Designers and uncheck option "Prevent saving changes that require table re-creation"...depending on your table size, you will probably have to set timeout, too. Your table will be locked during that time.
A solution for such problems is to have generate the ID using some kind of a sequence.
For example, in SQL Server you can create a sequence using the command below:
CREATE SEQUENCE Test.CountBy1
START WITH 1
INCREMENT BY 1 ;
GO
Then in C#, you can retrieve the next value out of Test and assign it to the ID before inserting it.
It sounds like you want a higher transaction isolation level or more restrictive locking.
I don't use these features too often, so hopefully somebody will suggest an edit if I'm wrong, but you want one of these:
-- specify the strictest isolation level
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
insert into XYZ values((SELECT max(ID)+1 from XYZ),'Name')
or
-- make locks exclusive so other transactions cannot access the same rows
insert into XYZ values((SELECT max(ID)+1 from XYZ WITH (XLOCK)),'Name')
I would like to have a primary key column in a table that is formatted as FOO-BAR-[identity number], for example:
FOO-BAR-1
FOO-BAR-2
FOO-BAR-3
FOO-BAR-4
FOO-BAR-5
Can SQL Server do this? Or do I have to use C# to manage the sequence? If that's the case, how can I get the next [identity number] part using EntityFramwork?
Thanks
EDIT:
I needed to do this is because this column represents a unique identifier of a notice send out to customers.
FOO will be a constant string
BAR will be different depending on the type of the notice (either Detection, Warning or Enforcement)
So is it better to have just an int identity column and append the values in Business Logic Layer in C#?
If you want this 'composited' field in your reports, I propose you to:
Use INT IDENTITY field as PK in table
Create view for this table. In this view you can additionally generate the field that you want using your strings and types.
Use this view in your repoorts.
But I still think, that there is BIG problem with DB design. I hope you'll try to redesign using normalization.
You can set anything as the PK in a table. But in this instance I would set IDENTITY to just an auto-incrementing int and manually be appending FOO-BAR- to it in the SQL, BLL, or UI depending on why it's being used. If there is a business reason for FOO and BAR then you should also set these as values in your DB row. You can then create a key in the DB between the two three columns depending on why your actually using the values.
But IMO I really don't think there is ever a real reason to concatenate an ID in such a fashion and store it as such in the DB. But then again I really only use an int as my ID's.
Another option would be to use what an old team I used to be on called a codes and value table. We didn't use it for precisely this (we used it in lieu of auto-incrementing identities to prevent environment mismatches for some key tables), but what you could do is this:
Create a table that has a row for each of your categories. Two (or more) columns in the row - minimum of category name and next number.
When you insert a record in the other table, you'll run a stored proc to get the next available identity number for that category, increment the number in the codes and values table by 1, and concatenate the category and number together for your insert.
However, if you're main table is a high-volume table with lots of inserts, it's possible you could wind up with stuff out of sequence.
In any event, even if it's not high volume, I think you'd be better off to reexamine why you want to do this, and see if there's another, better way to do it (such as having the business layer or UI do it, as others have suggested).
It is quite possible by using computed column like this:
CREATE TABLE #test (
id INT IDENTITY UNIQUE CLUSTERED,
pk AS CONCAT('FOO-BAR-', id) PERSISTED PRIMARY KEY NONCLUSTERED,
name NVARCHAR(20)
)
INSERT INTO #test (name) VALUES (N'one'), (N'two'), (N'three')
SELECT id, pk, name FROM #test
DROP TABLE #test
Note that pk is set to NONCLUSTERED on purpose because it is of VARCHAR type, while the IDENTITY field, which will be unique anyway, is set to UNIQUE CLUSTERED.