Optimistic Concurrency Control

This topic deals with optimistic concurrency control.

You can discover whether data has changed on the server while a user is in the process of editing the same data in a number of ways:

Using Timestamp for Concurrency Checks

The simplest mechanism for detecting that anything in a database row has changed is to use a timestamp (also known as rowversion) field. A timestamp is a binary field that is automatically updated whenever changes are made to any columns in the row. Many databases have a specific data type that is used for this. SQL Server uses the timestamp data type. With databases that do not have an explicit rowversion type, patterns are available for creating triggers to update fields in your database.

If you use timestamps field in your database, you have to bring them on the surface as properties in your entities. For example consider the following case. Suppose, you have a timestamp column in your table in the database. To consume it, you can execute the following steps:

  1. Add a field and a property in your persistent class for the timestamp column:

    The type of the field and the property should be Int64.

    public partial class Product
    {
        //The code for the rest of the fields and properties
    
        private long _timestamp;
        public virtual long Timestamp
        {
            get
            {
                return this._timestamp;
            }
            set
            {
                this._timestamp = value;
            }
        }
    }
    
    Public Partial Class Product
    
        ' The code for the rest of the fields and properties
    
        Private _timestamp As Long
        Public Overridable Property Timestamp As Long
            Get
                Return Me._timestamp
            End Get
            Set(ByVal value As Long)
                Me._timestamp = value
            End Set
        End Property
    
    End Class
    
  2. In the metadatasource class of your model, set the concurrency control strategy of the Product class to Backend:

    configuration.MapType(x => new { }).
        WithConcurencyControl(OptimisticConcurrencyControlStrategy.Backend).
        ToTable("Products");
    
    configuration.MapType(Function(x) New With {x}).
        WithConcurencyControl(OptimisticConcurrencyControlStrategy.Backend).
        ToTable("Products")
    
  3. Add a new property configuration for the timestamp column. Make sure to set it as version through the IsVersion method:

    configuration.HasProperty(x => x.Timestamp).HasFieldName("_timestamp").
        IsVersion().WithDataAccessKind(DataAccessKind.ReadWrite).
        ToColumn("Timestamp").IsNotNullable().HasColumnType("timestamp").
        HasPrecision(0).HasScale(0);
    
    configuration.HasProperty(x => x.Timestamp).HasFieldName("_timestamp").
        IsVersion().WithDataAccessKind(DataAccessKind.ReadWrite).
        ToColumn("Timestamp").IsNotNullable().HasColumnType("timestamp").
        HasPrecision(0).HasScale(0)
    

Using DateTime Column for Concurrency Check

In case your database legacy does not allow you to use a timestamp column, you could use a DateTime column to detect concurrent updates. The time of the operation is set on every insert or update, and the previous value is included in the where clause. This may not be safe if updates happen quicker than the resolution of the time field.

For example, consider the following case. Suppose, you have a DateTime column in your table in the database.

To consume it, you can execute the following steps:

  1. Add a field and a property in your persistent class for the DateTime column:

    The type of the field and the property should be DateTime.

    public partial class Product
    {
        //The code for the rest of the fields and properties
    
        private DateTime _timestamp;
        public virtual DateTime Timestamp
        {
            get
            {
                return this._timestamp;
            }
            set
            {
                this._timestamp = value;
            }
        }
    }
    
    Public Partial Class Product
    
        ' The code for the rest of the fields and properties
    
        Private _timestamp As DateTime
        Public Overridable Property Timestamp As DateTime
            Get
                Return Me._timestamp
            End Get
            Set(ByVal value As DateTime)
                Me._timestamp = value
            End Set
        End Property
    
    End Class
    
  2. In the metadatasource class of your model, set the concurrency control strategy of the Product class to Timestamp:

    configuration.MapType(x => new { }).
        WithConcurencyControl(OptimisticConcurrencyControlStrategy.Timestamp).
        ToTable("Products");
    
    configuration.MapType(Function(x) New With {x}).
        WithConcurencyControl(OptimisticConcurrencyControlStrategy.Timestamp).
        ToTable("Products")
    
  3. Add a new property configuration for the timestamp column. Make sure to set it as version through the IsVersion method:

    configuration.HasProperty(x => x.Timestamp).HasFieldName("_timestamp").
        IsVersion().WithDataAccessKind(DataAccessKind.ReadWrite).
        ToColumn("Timestamp").IsNotNullable().HasColumnType("datetime").
        HasPrecision(0).HasScale(0);
    
    configuration.HasProperty(x => x.Timestamp).HasFieldName("_timestamp").
        IsVersion().WithDataAccessKind(DataAccessKind.ReadWrite).
        ToColumn("Timestamp").IsNotNullable().HasColumnType("datetime").
        HasPrecision(0).HasScale(0)
    

Each time the record is updated, the datetime column will be updated automatically by Telerik Data Access.

Using Version Number for Concurrency Check

You could use a version column to detect concurrent updates. Usually the data type for the column is int. The version number is incremented on every update and the previous version number is included in the where clause. This is the fastest and safest optimistic concurrency control mode.

For example, consider the following case. Suppose, you have an int column in your table in the database (on the snapshot below, this is the Counter column).

To consume it, you can execute the following steps:

  1. Add a field and a property in your persistent class for the int column:

    The type of the field and the property should be Int32.

    public partial class Product
    {
        //The code for the rest of the fields and properties
    
        private int _counter;
        public virtual int Counter
        {
            get
            {
                return this._counter;
            }
            set
            {
                this._counter = value;
            }
        }
    }
    
    Public Partial Class Product
    
        ' The code for the rest of the fields and properties
    
        Private _counter As Integer
        Public Overridable Property Counter As Integer
            Get
                Return Me._counter
            End Get
            Set(ByVal value As Integer)
                Me._counter = value
            End Set
        End Property
    
    End Class
    
  2. In the metadatasource class of your model, set the concurrency control strategy of the Product class to Version:

    configuration.MapType(x => new { }).
        WithConcurencyControl(OptimisticConcurrencyControlStrategy.Version).
        ToTable("Products");
    
    configuration.MapType(Function(x) New With {x}).
        WithConcurencyControl(OptimisticConcurrencyControlStrategy.Version).
        ToTable("Products")
    
  3. Add a new property configuration for the timestamp column. Make sure to set it as version through the IsVersion method:

    configuration.HasProperty(x => x.Counter).HasFieldName("_counter").
        IsVersion().WithDataAccessKind(DataAccessKind.ReadWrite).
        ToColumn("Counter").IsNotNullable().HasColumnType("int").
        HasPrecision(0).HasScale(0);
    
    configuration.HasProperty(x => x.Counter).HasFieldName("_counter").
        IsVersion().WithDataAccessKind(DataAccessKind.ReadWrite).
        ToColumn("Counter").IsNotNullable().HasColumnType("int").
        HasPrecision(0).HasScale(0)
    

Checking for Any Changes

In case your database schema does not allow you any modifications (adding a version column for example), you could check for any changed columns. For example, if you set the Concurrency Mode to Changed, then all changed columns will be included in the where clause. This provides you more fine-grained optimistic concurrency control as different transactions may modify different fields of the same instance. Float and double fields are excluded as they are not exact (rounding procedures are different across databases and also differ from the way .NET is handling it).

The mapping configuration for the persistent class should use the Changed value of the OptimisticConcurrencyControlStrategy enum as a parameter of the WithConcurencyControl() method:

configuration.MapType(x => new { }).
    WithConcurencyControl(OptimisticConcurrencyControlStrategy.Changed).
    ToTable("Products");
configuration.MapType(Function(x) New With {x}).
    WithConcurencyControl(OptimisticConcurrencyControlStrategy.Changed).
    ToTable("Products")

If you set the Concurrency Mode to All, then the values of all columns (changed or not) will be included in the where clause.