As we mentioned earlier, data-bound controls store the values passed to the data source in independent Keys, Values (new values) and OldValues dictionaries. By default, SqlDataSource and ObjectDataSource ignore the OldValues field and only use Keys and Values. This behavior is detected by the data source's ConflictDetection property, which is set to OverwriteChanges by default. The OverwriteChanges pattern means "only match the primary key value in order to update or delete the record". This operation means that the update or deletion of a record does not take into account whether the underlying value of the record has changed. Under normal circumstances, the ideal state is to allow the Update or Delete operation to succeed only when the value of the data row exactly matches the originally selected value. In this ideal situation, if another user updates a row between the time you select it and the time you update it, your update operation will fail. The data source also supports this operation by setting the ConflictDetection property to CompareAllValues. In this mode, the data source will apply OldValues to the command or method, and it will use these values to ensure that the update or delete operation must match all values of the record before updating or deleting the record. You must also set the OldValuesParameterFormatString property to a valid .NET Framework component format string (such as "original_{0}") to indicate how parameters in the OldValues and Keys dictionaries should be renamed to distinguish them from NewValues parameters.
The following code example shows typical SQL commands used by the SqlDataSource control in OverwriteChanges and CompareAllValues modes. The ID field is assumed to be the primary key field. Note that the latter command compares all raw values of the data rows in the WHERE clause, rather than just the primary keys. In this case, the data source's OldValuesParameterFormatString needs to be set to "original_{0}".
SELECT [ID], [Name], [Address] from [Contacts]
-- OverwriteChanges
UPDATE [Contacts] SET [Name] = @Name, [Address] = @Address WHERE [ID] = @ID
DELETE FROM [Contacts] WHERE [ID] = @ID
-- CompareAllValues
UPDATE [Contacts] SET [Name] = @Name, [Address] = @Address WHERE [ID] = @original_ID
AND [Name] = @original_Name AND [Address] = @original_Address
DELETE FROM [Contacts] WHERE [ID] = @original_ID AND [Name] = @original_Name
AND [Address] = @original_Address
Please note that OldValues are not required for Insert operations, and ConflictDetection is only meaningful for Update and Delete operations.
The following example demonstrates the behavior when a conflict occurs. In order to run this example, you must open two instances of the example in two separate browser windows (click "Run Sample" twice). Then click the "Edit" button on the same row of both forms to put the row into edit mode. Change a value in the first window and click "Update". Please note that the update was successful. In the second window, enter a new value in the row and click "Update". This update operation is not successful because the value of the underlying data row has already been changed by the first update operation. This example checks the AffectedRows property of the Updated or Deleted event parameter, which is 0 to confirm that a conflict has occurred.
<script runat="server">
Protected Sub SqlDataSource1_Updated(sender As Object, e As SqlDataSourceStatusEventArgs)
If e.AffectedRows = 0 Then
Response.Write("Row changed, update aborted<br />")
End If
End Sub
Protected Sub SqlDataSource1_Deleted(sender As Object, e As SqlDataSourceStatusEventArgs)
If e.AffectedRows = 0 Then
Response.Write("Row changed, delete aborted<br />")
End If
End Sub
</script>
When Update or Delete uses the templated UI, the old values of the two-way data binding fields using the Bind syntax will be retained. For Delete, this means that you must use the Bind syntax for data-bound values in the ItemTemplate. The purpose is to retain the old value required for the delete operation. The following example demonstrates this technique.
<asp:GridView……>
<Columns>
<asp:CommandField ShowDeleteButton="True" ShowEditButton="True" />
<asp:TemplateField HeaderText="ContactID" InsertVisible="False" SortExpression="ContactID">
<ItemTemplate>
<asp:Label ID="Label1" runat="server" Text='<%# Bind("ContactID") %>'></asp:Label>
</ItemTemplate>
<EditItemTemplate>
<asp:Label ID="Label3" runat="server" Text='<%# Eval("ContactID") %>'></asp:Label>
</EditItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="ContactName" SortExpression="ContactName">
<ItemTemplate>
<asp:Label ID="Label2" runat="server" Text='<%# Bind("ContactName") %>'></asp:Label>
</ItemTemplate>
<EditItemTemplate>
<asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("ContactName") %>'></asp:TextBox>
</EditItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
You can handle conflict detection errors gently by prompting the user that the underlying data has been changed, displaying the changed value to the user, and allowing the user to choose to submit or abandon their operation. The following example demonstrates one possible way to handle conflict detection. Note that the DetailsView's RowUpdated event parameter is passed a dictionary that can be used to detect user-entered values. You can also set the KeepInEditMode property of this event parameter to keep the DetailsView in edit mode while the user decides how to handle the conflict. This example tests a similar approach to the previous one, opening two windows simultaneously to create conflicting updates.
Protected Sub DetailsView1_ItemUpdated(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.DetailsViewUpdatedEventArgs)
If e.AffectedRows = 0 Then
' Put DetailsView in edit mode and synchronize with the database e.KeepInEditMode = True
DetailsView1.DataBind()
' Repopulate the DetailsView with user-entered values
Dim t As TextBox
t = DetailsView1.Rows(1).Cells(1).Controls(0)
t.Text = e.NewValues("OrderDate")
t = DetailsView1.Rows(2).Cells(1).Controls(0)
t.Text = e.NewValues("ShipCountry")
ErrorPanel.Visible = True
Else
ErrorPanel.Visible = False
End If
End Sub
Protected Sub DetailsView1_ModeChanging(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.DetailsViewModeEventArgs)
If e.CancelingEdit = True AndAlso ErrorPanel.Visible = True Then
ErrorPanel.Visible = False
End If
The situation is similar when
End Sub
uses ObjectDataSource.Note that because the data source's ConflictDetection property is set to CompareAllValues, the data source will look for an UpdateContact overload that accepts the raw value of each field of the Contact object.
You can also use the DataObjectTypeName property and CompareAllValues together. In this case, the ObjectDataSource looks for an UpdateContact overload that accepts only two parameters (both Contact). The first parameter is the Contact object that stores the new value, and the second parameter is the Contact object that stores the old value.