-
Notifications
You must be signed in to change notification settings - Fork 227
RowVersion and TimeStamp columns
Simon Hughes edited this page Oct 12, 2022
·
2 revisions
Choosing the right entity configuration for the columns is essential. Here I show you the source tables, and the EF Core 6 configuration and the generated SQL to determine the correct answer.
CREATE TABLE VersionedNullable
(
Id INT NOT NULL IDENTITY(1, 1),
[Version] ROWVERSION NULL,
Number INT NOT NULL,
CONSTRAINT PK_VersionedNullable PRIMARY KEY (Id)
);
CREATE TABLE Versioned
(
Id INT NOT NULL IDENTITY(1, 1),
[Version] ROWVERSION NOT NULL,
Number INT NOT NULL,
CONSTRAINT PK_Versioned PRIMARY KEY (Id)
);
CREATE TABLE TimestampNotNull
(
Id INT NOT NULL IDENTITY(1, 1),
[Version] TIMESTAMP NOT NULL,
Number INT NOT NULL,
CONSTRAINT PK_TimestampNotNull PRIMARY KEY (Id)
);
CREATE TABLE TimestampNullable
(
Id INT NOT NULL IDENTITY(1, 1),
[Version] TIMESTAMP NULL,
Number INT NOT NULL,
CONSTRAINT PK_TTimestampNullable PRIMARY KEY (Id)
);
When executed, SQL Server converts the ROWVERSION to a TIMESTAMP. However, to be thorough I will test all scenarios.
In each test, the configuration was changed to include a combination of the following:
.ValueGeneratedOnAddOrUpdate()
.IsRowVersion()
.IsConcurrencyToken()
// VersionedNullable
builder.Property(x => x.Version)
.HasColumnName(@"Version")
.HasColumnType("timestamp")
.IsRequired(false);
// Versioned
builder.Property(x => x.Version)
.HasColumnName(@"Version")
.HasColumnType("timestamp(8)")
.IsRequired()
.IsFixedLength()
.HasMaxLength(8);
// TimestampNullable
builder.Property(x => x.Version)
.HasColumnName(@"Version")
.HasColumnType("timestamp")
.IsRequired(false);
// TimestampNotNull
builder.Property(x => x.Version)
.HasColumnName(@"Version")
.HasColumnType("timestamp(8)")
.IsRequired()
.IsFixedLength()
.HasMaxLength(8);
A row was added and .SaveChanges()
was called. Here is what worked (it inserted the row) and what didn't (threw an exception):
Column type | .ValueGeneratedOnAddOrUpdate | .IsRowVersion | .IsConcurrencyToken | INSERT worked |
---|---|---|---|---|
ROWVERSION NOT NULL | No | |||
ROWVERSION NOT NULL | x | No | ||
ROWVERSION NOT NULL | x | Yes | ||
ROWVERSION NOT NULL | x | x | Yes | |
ROWVERSION NOT NULL | x | Yes | ||
ROWVERSION NOT NULL | x | x | Yes | |
ROWVERSION NOT NULL | x | x | Yes | |
ROWVERSION NOT NULL | x | x | Yes | |
ROWVERSION NOT NULL | x | x | x | Yes |
ROWVERSION NULL | No | |||
ROWVERSION NULL | x | No | ||
ROWVERSION NULL | x | Yes | ||
ROWVERSION NULL | x | x | Yes | |
ROWVERSION NULL | x | Yes | ||
ROWVERSION NULL | x | x | Yes | |
ROWVERSION NULL | x | x | Yes | |
ROWVERSION NULL | x | x | Yes | |
ROWVERSION NULL | x | x | x | Yes |
TIMESTAMP NULL | No | |||
TIMESTAMP NULL | x | No | ||
TIMESTAMP NULL | x | Yes | ||
TIMESTAMP NULL | x | x | Yes | |
TIMESTAMP NULL | x | Yes | ||
TIMESTAMP NULL | x | x | Yes | |
TIMESTAMP NULL | x | x | Yes | |
TIMESTAMP NULL | x | x | Yes | |
TIMESTAMP NULL | x | x | x | Yes |
TIMESTAMP NOT NULL | No | |||
TIMESTAMP NOT NULL | x | No | ||
TIMESTAMP NOT NULL | x | Yes | ||
TIMESTAMP NOT NULL | x | x | Yes | |
TIMESTAMP NOT NULL | x | Yes | ||
TIMESTAMP NOT NULL | x | x | Yes | |
TIMESTAMP NOT NULL | x | Yes | ||
TIMESTAMP NOT NULL | x | x | Yes | |
TIMESTAMP NOT NULL | x | x | x | Yes |
A row was updated and the following SQL was generated
NULL or NOT NULL does not matter as the same SQL is generated.
SET NOCOUNT ON;
UPDATE [dbo].[TimestampNullable] SET [Number] = 4
WHERE [Id] = 9;
SELECT @@ROWCOUNT;
SET NOCOUNT ON;
UPDATE [dbo].[TimestampNullable] SET [Number] = 5
WHERE [Id] = 9;
SELECT [Version]
FROM [dbo].[TimestampNullable]
WHERE @@ROWCOUNT = 1 AND [Id] = 9;
SET NOCOUNT ON;
UPDATE [dbo].[TimestampNullable] SET [Number] = 6
WHERE [Id] = 9 AND [Version] = 0x0000000000004681;
SELECT [Version]
FROM [dbo].[TimestampNullable]
WHERE @@ROWCOUNT = 1 AND [Id] = 9;
SET NOCOUNT ON;
UPDATE [dbo].[TimestampNullable] SET [Number] = 7
WHERE [Id] = 9 AND [Version] = 0x0000000000004682;
SELECT @@ROWCOUNT;
SET NOCOUNT ON;
UPDATE [dbo].[TimestampNullable] SET [Number] = 8
WHERE [Id] = 9 AND [Version] = 0x0000000000004683;
SELECT [Version]
FROM [dbo].[TimestampNullable]
WHERE @@ROWCOUNT = 1 AND [Id] = 9;
-
.ValueGeneratedOnAddOrUpdate()
is not the right option to choose because it does not check the version during an update. However, it allows a row to be inserted. -
.IsRowVersion()
works correctly for bothROWVERSION
andTIMESTAMP
columns types. -
.IsConcurrencyToken()
works for the insert, but does not read back the latest value for theROWVERSION
/TIMESTAMP
.
The answer is to use a both .IsRowVersion()
and .IsConcurrencyToken()
in all cases:
- Column type is a ROWVERSION or a TIMESTAMP
- Column is NULL or NOT NULL