使用 Entity Framework Core 2.1 插入时间 table 时出错

Error when inserting into temporal table using Entity Framework Core 2.1

尝试使用 Entity Framework Core 2.1.

插入时间 table 时出现以下错误

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.

下面是我的table架构

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)   
   )   
;

我的 DbContext 看起来像这样:

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");
            });

请查看并告诉我如何解决此问题。我不喜欢修改数据库上下文 class,因为我使用 DBScaffold 通过 nuget 控制台生成它,并且每次重新生成它时我都无法手动更新它。

创建期间开始列 (SysStartTime) 和期间结束列 (SysEndTime) 应该可以解决此问题。我们可以通过

ALTER TABLE [dbo].[Department] ALTER COLUMN [SysStartTime] ADD HIDDEN;
ALTER TABLE [dbo].[Department] ALTER COLUMN [SysEndTime] ADD HIDDEN;

我们可以在 sys.columns table

中看到这些列的隐藏设置
SELECT * FROM sys.columns WHERE is_hidden = 1 

长话短说:

  • OnModelCreating 代码中为 "valid from" 列添加 entity.Property(e => e.Modified).HasComputedColumnSql("GENERATED ALWAYS AS ROW START");
  • 您不需要为您的 "valid to" 专栏做任何事情。

背景:

我正在使用 .NET Core 2.2 和 EntityFrameworkCore 3.0 预览版 6。

而不是将 HIDDEN 属性添加到 @VPP 的答案中的那些列,我发现添加 HasComputedColumnSql 可以做到这一点 它也意味着 EF在每个 UPDATE.

之后检索列

这是我的 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));

这是我的模型构建器代码的样子(最初由 dotnet ef dbcontext scaffold 生成,并标记了我的修改):

    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");
        });

简而言之:

  • 为您的 "valid from" 列添加 entity.Property(e => e.Modified).HasComputedColumnSql("GENERATED ALWAYS AS ROW START");
  • 您不需要为您的 "valid to" 专栏做任何事情。

我 运行 测试和 INSERTUPDATE 语句工作正常(请注意,我的代码根本不会修改我的 "valid from" 或 "valid to" 属性).

如上所述,这种方法的优点是在您调用 SaveChanges/SaveChangesAsync 后,EF 将自动获取 "valid from" 列的 updated/new 值.

您可以将时间日期有效 to/from 列设置为 Is Hidden = True。然后 EF 不在乎,您的临时版本无需任何额外工作即可工作。当 EF 支持它(3.0ish)时,您应该能够使用 .FromSql($"SELECT * FROM dbo.Products FOR SYSTEM_TIME AS OF {{0}}", date.ToUniversalTime()) 来过滤该数据,但大多数前端只关心最近的数据,因此它可能不会对我们大多数人来说都是一个问题。