I'm getting the below error when trying to insert into a temporal table using Entity Framework Core 2.1.
Cannot insert an explicit value into a GENERATED ALWAYS column in table 'db_test.dbo.Department'. Use INSERT with a column list to exclude the GENERATED ALWAYS column, or insert a DEFAULT into GENERATED ALWAYS column.
Below is my table schema
CREATE TABLE Department
(
DeptID int NOT NULL PRIMARY KEY CLUSTERED,
DeptName varchar(50) NOT NULL,
ManagerID INT NULL,
ParentDeptID int NULL,
SysStartTime datetime2 GENERATED ALWAYS AS ROW START NOT NULL,
SysEndTime datetime2 GENERATED ALWAYS AS ROW END NOT NULL,
PERIOD FOR SYSTEM_TIME (SysStartTime, SysEndTime)
)
WITH
(
SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.DepartmentHistory)
)
;
And my DbContext
looks like this:
modelBuilder.Entity<Department>(entity =>
{
entity.HasKey(e => e.DeptId);
entity.Property(e => e.DeptId)
.HasColumnName("DeptID")
.ValueGeneratedNever();
entity.Property(e => e.DeptName)
.IsRequired()
.HasMaxLength(50)
.IsUnicode(false);
entity.Property(e => e.ManagerId).HasColumnName("ManagerID");
entity.Property(e => e.ParentDeptId).HasColumnName("ParentDeptID");
});
Please take a look and let me know how we can fix this. I'm not a fan of modifying the db context class as I generate it via nuget console using DBScaffold and I cannot update it manually each time when we re-generate it.
Making the Period start column(SysStartTime) and Period end column(SysEndTime) should fix this issue. We can do this by
ALTER TABLE [dbo].[Department] ALTER COLUMN [SysStartTime] ADD HIDDEN;
ALTER TABLE [dbo].[Department] ALTER COLUMN [SysEndTime] ADD HIDDEN;
We can see the settings for hidden against these columns in the sys.columns table
SELECT * FROM sys.columns WHERE is_hidden = 1
TL;DR:
- Add
entity.Property(e => e.Modified).HasComputedColumnSql("GENERATED ALWAYS AS ROW START");
for your "valid from" column in yourOnModelCreating
code. - You do not need to do anything for your "valid to" column.
Background:
I'm using .NET Core 2.2 with EntityFrameworkCore 3.0 Preview 6.
Rather than adding the HIDDEN
attribute to those columns in @VPP's answer, I found that adding HasComputedColumnSql
does the trick and it also means EF retrieves the column after each UPDATE
.
Here's my CREATE TABLE
:
CREATE TABLE [dbo].[Contacts] (
[ContactId] INT NOT NULL IDENTITY,
[TenantId] INT NOT NULL,
[Created] DATETIME2 (7) NOT NULL,
[Modified] DATETIME2 (7) GENERATED ALWAYS AS ROW START NOT NULL,
[ValidTo] DATETIME2 (7) GENERATED ALWAYS AS ROW END NOT NULL CONSTRAINT [DF_Contacts_ValidTo] DEFAULT ('9999-12-31 23:59:59.9999999'),
[Name] NVARCHAR(255) NOT NULL,
[PhoneNumber] NVARCHAR(255) NOT NULL,
[HasAMulletHaircut] BIT NOT NULL,
[ListensToDevo] BIT NOT NULL,
[Status] INT NOT NULL,
CONSTRAINT PK_Contacts PRIMARY KEY ([ContactId], [TenantId]),
CONSTRAINT [FK_Contacts_Tenants] FOREIGN KEY ([TenantId]) REFERENCES dbo.Tenants( TenantId ),
PERIOD FOR SYSTEM_TIME ([Modified], [ValidTo])
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE=[dbo].[Contacts_History], DATA_CONSISTENCY_CHECK=ON));
Here's what my Model Builder code looks like (originally generated by dotnet ef dbcontext scaffold
, with my modifications labelled):
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Contact>(entity =>
{
entity.HasKey(e => new { e.ContactId, e.TenantId });
// I added this:
entity.Property(e => e.Modified).HasComputedColumnSql("GENERATED ALWAYS AS ROW START");
// I added this, then commented it out because properties cannot have both `HasComputedColumnSql` and `HasDefaultValueSql`. But it works anyway.
//entity.Property(e => e.ValidTo).HasComputedColumnSql("GENERATED ALWAYS AS ROW END");
entity.Property(e => e.ValidTo).HasDefaultValueSql("('9999-12-31 23:59:59.9999999')");
entity.HasOne(d => d.Tenant)
.WithMany(p => p.Contacts)
.HasForeignKey(d => d.TenantId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_Contacts_Tenants");
});
In short:
- Add
entity.Property(e => e.Modified).HasComputedColumnSql("GENERATED ALWAYS AS ROW START");
for your "valid from" column. - You do not need to do anything for your "valid to" column.
I ran tests and INSERT
and UPDATE
statements work fine (note that my code does not modify my "valid from" or "valid to" properties at all).
As mentioned above, the advantage of this approach is that after you call SaveChanges
/SaveChangesAsync
, EF will fetch the updated/new values for the "valid from" column automatically.
You can set the temporal date valid to/from columns to Is Hidden = True. Then EF does not care and your temporal version works without any extra work. When EF supports it (3.0ish) you should be able to use .FromSql($"SELECT * FROM dbo.Products FOR SYSTEM_TIME AS OF {{0}}", date.ToUniversalTime()) to filter that data but most front ends only care about the recent data so it might not be an issue for most of us.
来源:https://stackoverflow.com/questions/52654609/error-when-inserting-into-temporal-table-using-entity-framework-core-2-1