There are two approaches of handling the process in the appropriate manner - the first involves using OpenAccessDataSource component for managing CRUD operations. All the logic for detecting and exposing a concurrent update or delete is built in the OpenAccessDataSurce and it is very straight-forward to track concurrent issues there.
When detecting such problem during an update or delete operation OpenAccessDataSource throws an OptimisticVerificationException which can be handled in the event handlers for the updating and deleting events.
In more advanced web projects though using a data source is not always an option. Queries are executed in code-behind and all CRUD operations are handled in that manner.
The answer for all concurrency concerns in those situations comes with the implementation of the IDataObjectKey interface to the persistent classes that are to be modified or deleted.
The IDataObjectKey property is a unique string for every persistent class that contains information about the identity and version of the object. Because the version of an object is updated on modification this key can be used to give us information on weather a certain object has been modified.
Sample implementation:
There are entities of the Person type to be presented in a RadGrid. The grid is bound with a simple Linq query returning all the Person objects from the database.
Our Person class implements the IDataObjectKey interface:
C# |
Copy Code |
[Telerik.OpenAccess.Persistent(IdentityField = "id")] public class Person:IDataObjectKey { private int id; [FieldAlias("id")] public int Id { get { return id; } set { id = value; } } ...
public string DataObjectKey { get{return Telerik.OpenAccess.DataObjectKey.Obtain(this);} } } |
VB .NET |
Copy Code |
Public Class Person Implements IDataObjectKey Private id_Renamed As Integer <FieldAlias("id")> _ Public Property Id() As Integer Get Return id_Renamed End Get Set(ByVal value As Integer) id_Renamed = value End Set End Property ... Public ReadOnly Property DataObjectKey() As String Get Return Telerik.OpenAccess.DataObjectKey.Obtain(Me) End Get End Property End Class |
The RadGrid has the edit command enabled. Our solution for the concurrent updates is the following:
• When the Edit button is pressed we obtain the person object to be updated and store its DataObjectKey property into the Session state. We do that operation in the EditCommand event handler of the RadGrid:
C# |
Copy Code |
protected void RadGrid1_EditCommand(object source, Telerik.Web.UI.GridCommandEventArgs e) { IDictionary values = new Hashtable(); ((GridDataItem)e.Item).ExtractValues(values); string id = (string)values["Id"]; Person updatingPerson = (Person)scope.GetObjectById(Database.OID.ParseObjectId(typeof(Person), id)); string keyAndVersion = updatingPerson.DataObjectKey; Session["updatingPerson" + id.ToString()] = keyAndVersion; } |
VB .NET |
Copy Code |
Protected Sub RadGrid1_EditCommand(ByVal source As Object, ByVal e As Telerik.Web.UI.GridCommandEventArgs) Dim values As IDictionary = New Hashtable() CType(e.Item, GridDataItem).ExtractValues(values) Dim id As String = CStr(values("Id")) Dim updatingPerson As Person = CType(scope.GetObjectById(Database.OID.ParseObjectId(GetType(Person), id)), Person) Dim keyAndVersion As String = updatingPerson.DataObjectKey Session("updatingPerson" & id.ToString()) = keyAndVersion End Sub |
• After typing the new values into the edit form of the grid the Update button is pressed. We obtain the record once again in the code-behind but this time using the static DataObjectKey.Check(string dataObjectKey, IObjectCotnext context) method. In this method OpenAccess checks if the instance represented by the DataObjectKey has been modified since the DataObjectKey was obtained. If the instance contains a new DataObjectKey e.g. it has a new version, an OptimisticVerificationException is thrown, else the object is returned and we can commit an update on it.
C# |
Copy Code |
protected void RadGrid1_UpdateCommand(object source, GridCommandEventArgs e) { IDictionary values = new Hashtable(); ((GridEditableItem)e.Item).OwnerTableView.ExtractValuesFromItem(values,((GridEditableItem)e.Item));
string id = (string)values["Id"]; string keyAndVersionOld = (string)Session["updatingPerson" + id.ToString()]; Person updatingPerson = (Person)DataObjectKey.Check(keyAndVersionOld, scope); if (updatingPerson != null) { scope.Transaction.Begin(); updatingPerson.Id = int.Parse(values["Id"].ToString()); updatingPerson.FirstName = values["FirstName"].ToString(); updatingPerson.LastName = values["LastName"].ToString(); updatingPerson.AddressId = int.Parse(values["AddressId"].ToString()); scope.Transaction.Commit(); Session["updatingPerson" + id.ToString()] = null; } } |
VB .NET |
Copy Code |
Protected Sub RadGrid1_UpdateCommand(ByVal source As Object, ByVal e As GridCommandEventArgs) Dim values As IDictionary = New Hashtable() CType(e.Item, GridEditableItem).OwnerTableView.ExtractValuesFromItem(values,(CType(e.Item, GridEditableItem))) Dim id As String = CStr(values("Id")) Dim keyAndVersionOld As String = CStr(Session("updatingPerson" & id.ToString())) Dim updatingPerson As Person = CType(DataObjectKey.Check(keyAndVersionOld, scope), Person) If updatingPerson IsNot Nothing Then scope.Transaction.Begin() updatingPerson.Id = Integer.Parse(values("Id").ToString()) updatingPerson.FirstName = values("FirstName").ToString() updatingPerson.LastName = values("LastName").ToString() updatingPerson.AddressId = Integer.Parse(values("AddressId").ToString()) scope.Transaction.Commit() Session("updatingPerson" & id.ToString()) = Nothing End If End Sub |
From this point on it is fairly easy for one to implement his/her own logic for proceeding with the situation. For example the Check() method can be called in a try/catch block so if there is a concurrency problem we can just prompt the user with appropriate message.