I'm creating the default concurrency strategy that I will use in my application.
I decided for an optimistic strategy.
All of my entities are mapped as Table per Type (TPT)
(using inheritance). I soon learned that there is a problem when using columns of type RowVersion with inheritance on Entity Framework:
Product
Id INT IDENTITY PRIMARY KEY
RowVersion ROWVERSION
Car (inherits Product records)
Color TYNIINT NOT NULL,
AnotherProperty....
If I update a record of the Car
table the RowVersion column from Product
table will not be updated.
I plan to use a column of type datetime2 (7)
in Product
and update it manually if any records of the tables that inherit this table are modified.
I think I'm reinventing the wheel.
Is there another way to use the optimistic concurrency strategy with ROWVERSION
when using Table per Type (TPT)
in Entity Framework?
Edit
My mapping:
class Product
{
int Id { get; set; }
string Name { get; set; }
byte[] RowVersion { get; set; }
}
class Car : Product
{
int Color { get; set; }
}
CodeFirst conventions.
Only the RowVersion property on Product
entity has custom definitions:
modelBuilder.Entity<Product>()
.Property(t => t.RowVersion)
.IsConcurrencyToken();
Both in EF6 and EF-core, when working with Sql Server, you have to use this mapping:
IsConcurrencyToken does configure a property as concurrency token, but (when using it for a
byte[]
property)varbinary(max)
null
if you don't initialize itIsRowVersion on the other hand,
rowversion
(in Sql Server, ortimestamp
in earlier versions), soNow when you update a
Car
you'll see two update statements:The first statement doesn't update anything, but it increments the rowversion, and it will throw a concurrency exception if the rowversion was changed in-between.
The
[System.ComponentModel.DataAnnotations.Schema.Timestamp]
attribute is the data annotations equivalent ofIsRowVersion()
:After a bit of investigating I was able to use IsConcurrencyToken on a byte[8] column called RowVersion in Entity Framework 6.
Because we want to use the same datatype in DB2 ( which doesn't have rowversion in the database itself) we can't use the option IsRowVersion()!
I investigated a little bit further how to work with IsConcurrencyToken.
I did the following to achieve a solution that seems to work:
My Model:
IConcurrencyEnabled is used to identify Entities that have a rowversion that needs special treatment.
I used fluent API to configure the modelbuilder:
And finally I added a method to my derived DBContext class to update the field before the base.SaveChanges is called:
The problem most people encounter is that after setting the value of the entity, we always get a UpdateDBConcurrencyException, because the OriginalValue has changed... even if it hasn't!
The reason is that for a byte[] both original and currentValue change if you set the CurrentValue alone (?? strange and unexpected behavior).
So I set the OriginalValue again to the original Value before I updated the rowversion... Also I copy the array to avoid referencing the same byte-array!
Attention: Here I use an incremental approach to change the rowversion, you are free to use your own strategy to fill in this value. (Random or time-based)
The problem is not how you are setup. What is happening is that the
OriginalValue
of yourRowVersion
entry is set to the new value as soon as you pull it out of the Context.