Both in EF6 and EF-core, when working with SQL Server, you have to use this mapping:
modelBuilder.Entity<Product>()
.Property(t => t.RowVersion)
.IsRowVersion(); // Not: IsConcurrencyToken
IsConcurrencyToken does configure a property as concurrency token, but (when using it for a byte[]
property)
- the data type is
varbinary(max)
- its value is always
null
if you don’t initialize it - its value is not auto-incremented when a record is updated.
IsRowVersion on the other hand,
- has datatype
rowversion
(in SQL Server, ortimestamp
in earlier versions), so - its value is never null, and
- its value is always auto-incremented when a record is updated.
- and it automatically configures the property to be an optimistic concurrency token.
Now when you update a Car
you’ll see two update statements:
DECLARE @p int
UPDATE [dbo].[Product]
SET @p = 0
WHERE (([Id] = @0) AND ([Rowversion] = @1))
SELECT [Rowversion]
FROM [dbo].[Product]
WHERE @@ROWCOUNT > 0 AND [Id] = @0
UPDATE [dbo].[Car]
SET ...
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 of IsRowVersion()
:
[Timestamp]
public byte[] RowVersion { get; set; }
Note that the official documentation is not correct. It says that IsConcurrencyToken
is the fluent equivalent of the [Timestamp]
attribute. However, IsRowVersion
is the equivalent.