Optimistic Concurrency

Optimistic concurrency can be added to any table by adding the ulong RowVersion { get; set; } property, e.g:

public class Poco
{
    ...
    public ulong RowVersion { get; set; }
}

RowVersion is implemented efficiently in all major RDBMS's, i.e:

  • Uses rowversion datatype in SqlServer
  • Uses PostgreSql's xmin system column (no column on table required)
  • Uses UPDATE triggers on MySql, Sqlite and Oracle whose lifetime is attached to Create/Drop tables APIs

Despite their differing implementations each provider works the same way where the RowVersion property is populated when the record is selected and only updates the record if the RowVersion matches with what's in the database, e.g:

var rowId = db.Insert(new Poco { Text = "Text" }, selectIdentity:true);

var row = db.SingleById<Poco>(rowId);
row.Text += " Updated";
db.Update(row); //success!

row.Text += "Attempting to update stale record";

//Can't update stale record
Assert.Throws<OptimisticConcurrencyException>(() =>
    db.Update(row));

//Can update latest version
var updatedRow = db.SingleById<Poco>(rowId);  // fresh version
updatedRow.Text += "Update Success!";
db.Update(updatedRow);

updatedRow = db.SingleById<Poco>(rowId);
db.Delete(updatedRow);                        // can delete fresh version

Optimistic concurrency is only verified on API's that update or delete an entire entity, i.e. it's not enforced in partial updates. There's also an Alternative API available for DELETE's:

db.DeleteById<Poco>(id:updatedRow.Id, rowversion:updatedRow.RowVersion)

RowVersion Byte Array

To improve reuse of OrmLite's Data Models in Dapper, OrmLite also supports byte[] RowVersion which lets you use OrmLite Data Models with byte[] RowVersion properties in Dapper queries.

Conflict Resolution using commandFilter

An optional Func<IDbCommand> commandFilter is available in all INSERT and UPDATE APIs to allow customization and inspection of the populated IDbCommand before it's run. This feature is utilized in the Conflict Resolution Extension methods where you can specify the conflict resolution strategy when a Primary Key or Unique constraint violation occurs:

db.InsertAll(rows, dbCmd => dbCmd.OnConflictIgnore());

//Equivalent to: 
db.InsertAll(rows, dbCmd => dbCmd.OnConflict(ConflictResolution.Ignore));

In this case it will ignore any conflicts that occur and continue inserting the remaining rows in SQLite, MySql and PostgreSQL, whilst in SQL Server it's a NOOP.

SQLite offers additional fine-grained behavior that can be specified for when a conflict occurs:

  • ROLLBACK
  • ABORT
  • FAIL
  • IGNORE
  • REPLACE