Monday, March 19, 2012

Optimistic Concurrency Help

Hi,

I'm trying to implement Optimistic Concurrency in asp 2 but so far it has caused me nothing but problems.

First, when doing an UPDATE I tried to use the primary key & a timestamp field which I had in SQL Express.. VS 2005 generated the stored procedures fine however when it came to the actual updating I think there was a problem with the conversion of the timestamp field when it was being stored in a text box (in a FormView control). So.. as a result that failed. And also I checked sooo many places online and haven't been able to find any examples of code where a timestamp was used with success in asp2.

Next, I got ride of the timestamp type (in SQL Express database) and used a datetime and then.. I just implemented Optimistic Concurrency by passing in ALL the values (ie all the original values) like is proposed http://www.asp.net/learn/dataaccess/tutorial21vb.aspx?tabid=63 . This... works however I really do not want to have to pass in ALL these values (ie original and new).

Ideally I would like to be able to use the primary key & the datetime field to handle the Optimistic Concurrency checks where only the original values of both those fields are passed back into the stored procedure. Now.. I tried this as well, but I kept getting an error that suggests that (for some reason) the FormView or DataSource is passing ALL the values (original & new) into the dataset as opposed to only the original primary key & datetime fields & the new set of values.

Can ANYONE offer any help? I really would like not to have to pass in all these values.

Thanks in advance!

Why not add a "last_updated" column that is of type uniqueidentiifer (GUID) and use the NEWID() function to create a new guid whenever the table is updated. Obviously you would need to wrap this in a stored proc that all of your client code uses instead of the table direclty. (or you could use a UPDATE trigger).

Then just have the app compare the guid it retrieved on first read to the one on the row when it goes to do the update. If they dont match, you are done. Make sure to wrap the code in a transaction when you first a) check the guid to see if they match, then b) perform the actual update.

-David

|||

Yes, although you will have to set up your stored procedures, and sqldatasource control manually.

First, a timestamp is not a datetime. It's technically a binary object, although it can be converted to/from an integer pretty easily. Also, you can't SET the value of a timestamp field. The whole point of a timestamp column is that the database maintains it. In it's current implementation (logically), every table that has a timestamp column maintains a counter at the table-level. Every time a row is changed, it increments this counter, and uses that value as the timestamp value. So every row that gets changed has a unique number.

As for the error about the datasource/formview passing ALL the values, it's most likely because you have the datasource's conflict detection property set to "CompareAllValues". Change it to "Overwrite". Then only the keys you've specified in the formview/gridview's datakeynames property, and the fields you've bound via a 2-way databind should be passed back.

|||

Thanks for the responses!

dwlovell - Even if I use GUID instead of timestamp or datetime, I still have the prob with the update and getting the parameters passed nicely from the datasource to the stored procedure. Unless... If I put a GUID in the table.. and then in VS 2005 chose optimistic concurrency (when creating the tableadapter and stored procedures).. will VS 2005 recognise the GUID field (like it does with the timestamp field) and therefore use only the table key & that GUID fields to compare for the update??

Motley - I know that timestamp and datetime are different, I only used datetime because I (a) timestamp wasn't working and (b) datetime could be used (when tweeked) to implement the optimistic concurrency.

To avoid the error about passing all the values, I had used OverwriteChanges before but my problem with that is that I don't think the datetime field's original value is being passed to the stored procedure successfully. The only original value being passed was the key. Therefore, I'm getting a "SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM." error constantly (I believe that 0/0/0000 00:00:00 - i.e. an invalid value - is being passed from the dataset to the sql stored procedure). This error is linked with the following LOC in the stack trace: "int returnValue = this.Adapter.UpdateCommand.ExecuteNonQuery();".

The strange thing is that when I check the FormView1_ItemUpdating's event, I can see the datatime field's values in both e.NewValues & e.OldValues.. so I can't understand why the value is not being passed to the stored procedure.

A snippet from my <EditItemTemplate> (in the FormView) is below:

 <asp:TextBox ID="RTextBox" runat="server" Text='<%# Bind("R")%>'>
</asp:TextBox><br />
RDescription:
<asp:TextBox ID="RDescriptionTextBox" runat="server" Text='<%# Bind("RDescription")%>'>
</asp:TextBox><br />
Lastupdate:
<asp:TextBox ID="LastupdateTextBox" runat="server" Text='<%# Bind("Lastupdate")%>'>
</asp:TextBox>
...

A snippet from my ObjectDataSource is below:

<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" ConflictDetection="OverwriteChanges"
DeleteMethod="Delete" InsertMethod="Insert" OldValuesParameterFormatString="original_{0}"
SelectMethod="GetData" TypeName="DataSet1TableAdapters.RTableAdapter" UpdateMethod="Update">
<UpdateParameters>
<asp:Parameter Name="R" Type="Byte" />
<asp:Parameter Name="RDescription" Type="String" />
<asp:Parameter Name="Lastupdate" Type="DateTime" />
<asp:Parameter Name="original_R" Type="Byte" />
<asp:Parameter Name="original_Lastupdate" Type="DateTime" />
</UpdateParameters>
...

Here's a bit from the <UpdateCommand> in the DataSet (not sure it's relevant but.. doesn't hurt and maybe one of you gurus will find an answer to my prob there..):

 <Parameters>
<Parameter AllowDbNull="False" AutogeneratedName="" DataSourceName="" DbType="Object" Direction="ReturnValue" ParameterName="@.RETURN_VALUE" Precision="0" Scale="0" Size="0" SourceColumnNullMapping="False" SourceVersion="Current">
</Parameter>
<Parameter AllowDbNull="False" AutogeneratedName="" DataSourceName="" DataTypeServer="tinyint" DbType="Byte" Direction="Input" ParameterName="@.R" Precision="0" ProviderType="TinyInt" Scale="0" Size="0" SourceColumn="R" SourceColumnNullMapping="False" SourceVersion="Current">
</Parameter>
<Parameter AllowDbNull="False" AutogeneratedName="" DataSourceName="" DataTypeServer="varchar(40)" DbType="AnsiString" Direction="Input" ParameterName="@.RDescription" Precision="0" ProviderType="VarChar" Scale="0" Size="0" SourceColumn="RDescription" SourceColumnNullMapping="False" SourceVersion="Current">
</Parameter>
<Parameter AllowDbNull="False" AutogeneratedName="" DataSourceName="" DataTypeServer="datetime" DbType="DateTime" Direction="Input" ParameterName="@.Lastupdate" Precision="0" ProviderType="DateTime" Scale="0" Size="0" SourceColumn="Lastupdate" SourceColumnNullMapping="False" SourceVersion="Current">
</Parameter>
<Parameter AllowDbNull="False" AutogeneratedName="" DataSourceName="" DataTypeServer="tinyint" DbType="Byte" Direction="Input" ParameterName="@.Original_R" Precision="0" ProviderType="TinyInt" Scale="0" Size="0" SourceColumn="R" SourceColumnNullMapping="False" SourceVersion="Original">
</Parameter>
<Parameter AllowDbNull="False" AutogeneratedName="" DataSourceName="" DataTypeServer="datetime" DbType="DateTime" Direction="Input" ParameterName="@.Original_Lastupdate" Precision="0" ProviderType="DateTime" Scale="0" Size="0" SourceColumn="Lastupdate" SourceColumnNullMapping="False" SourceVersion="Original">
</Parameter>

So... any more thoughts??|||

You might try posting this in the ObjectDatasource forum.

You might also try adding a parameter at the top of your list in the ObjectDataSource for the return value. It's possible the parameters are being shifted by one which would obviously try and force a different value into your datetime column and give the error you see (I could be completely wrong on that, I'm not a ObjectDataSource expert).

If this was a SqlDataSource, I would suggest catching the SqlDataSource_Updating event, and check the command object (and it's parameters), to make sure they all look correct at that stage.

|||

Well you definitely have shed some light on this! And I thank you greatly for it! (See, even though you're not an ObjectDataSource expert, you still helped!!Smile) I checked the ObjectDataSource_Updating event and the original parameter for the Lastupdate was 'Nothing'.

This clearly indicates that the parameter is not being passed to the stored procedure and instead, is somehow being lost between the FormView and the ObjectDataSource. I really don't have a clue why this is happening though! To me, it's like (for optimistic concurrency) the ObjectDataSource control is to work when either ALL the original values are being passed in (i.e. ConflictDetection="CompareAllValues") or just the Key (i.e. ConflictDetection="OverwriteChanges") for the update. There seems to be no "mid-point" where one can send only a specific subset of original values to the ObjectDataSourceautomatically.. which really is a bummer!

Now what I'll have to do is get the original Lastupdate value from the form and set Lastupdate input parameter to that value in the ObjectDataSource_Updating event.

I've also taken your suggestion and posted this question in the ObjectDatasource forum.Here's the link for those of you interested.

Thanks again Motley!

|||

I've written a tutorial to address this exact issue... after spending days figuring it all out.

http://www.primedigit.com/implementing-optimistic-concurrency-with-sql-timestamps/

No comments:

Post a Comment