Error when inserting into temporal table using Entity Framework Core 2.1

断了今生、忘了曾经 提交于 2019-12-08 02:27:03

问题


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.


回答1:


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 



回答2:


TL;DR:

  • Add entity.Property(e => e.Modified).HasComputedColumnSql("GENERATED ALWAYS AS ROW START"); for your "valid from" column in your OnModelCreating 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.




回答3:


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!