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
rowversiondatatype in SqlServer - Uses PostgreSql's
xminsystem 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