5

工作我使用的数据库第一实体框架6.改变某些表在我的架构后进行时态表,我开始试图插入新的数据时,出现以下错误:实体框架不与时态表

Cannot insert an explicit value into a GENERATED ALWAYS column in table '<MyDatabase>.dbo.<MyTableName>. Use INSERT with a column list to exclude the GENERATED ALWAYS column, or insert a DEFAULT into GENERATED ALWAYS column.

看起来EF正试图更新由系统管理的PERIOD列的值。

从EDMX文件中删除列似乎可以解决问题,但这不是一个可行的解决方案,因为每次从数据库重新生成模型时都会重新添加列。

回答

7

有两个解决这个问题:

  1. 在在EDMX设计师的列的属性窗口,更改的PERIODStoreGeneratedPattern(ValidFrom和ValidTo在我的情况中)为identitycomputed。标识可能会更好,因为计算结果会导致EF刷新插入和更新上的值,而不仅仅是插入identity
  2. 创建IDbCommandTreeInterceptor实现以删除周期列。这是我首选的解决方案,因为向模型添加新表格时不需要额外的工作。

这里是我的实现:

using System.Data.Entity.Infrastructure.Interception; 
using System.Data.Entity.Core.Common.CommandTrees; 
using System.Data.Entity.Core.Metadata.Edm; 
using System.Collections.ObjectModel; 

internal class TemporalTableCommandTreeInterceptor : IDbCommandTreeInterceptor 
{ 
    private static readonly List<string> _namesToIgnore = new List<string> { "ValidFrom", "ValidTo" }; 

    public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext) 
    { 
     if (interceptionContext.OriginalResult.DataSpace == DataSpace.SSpace) 
     { 
      var insertCommand = interceptionContext.Result as DbInsertCommandTree; 
      if (insertCommand != null) 
      { 
       var newSetClauses = GenerateSetClauses(insertCommand.SetClauses); 

       var newCommand = new DbInsertCommandTree(
        insertCommand.MetadataWorkspace, 
        insertCommand.DataSpace, 
        insertCommand.Target, 
        newSetClauses, 
        insertCommand.Returning); 

       interceptionContext.Result = newCommand; 
      } 

      var updateCommand = interceptionContext.Result as DbUpdateCommandTree; 
      if (updateCommand != null) 
      { 
       var newSetClauses = GenerateSetClauses(updateCommand.SetClauses); 

       var newCommand = new DbUpdateCommandTree(
        updateCommand.MetadataWorkspace, 
        updateCommand.DataSpace, 
        updateCommand.Target, 
        updateCommand.Predicate, 
        newSetClauses, 
        updateCommand.Returning); 

       interceptionContext.Result = newCommand; 
      } 
     } 
    } 

    private static ReadOnlyCollection<DbModificationClause> GenerateSetClauses(IList<DbModificationClause> modificationClauses) 
    { 
     var props = new List<DbModificationClause>(modificationClauses); 
     props = props.Where(_ => !_namesToIgnore.Contains((((_ as DbSetClause)?.Property as DbPropertyExpression)?.Property as EdmProperty)?.Name)).ToList(); 

     var newSetClauses = new ReadOnlyCollection<DbModificationClause>(props); 
     return newSetClauses; 
    } 
} 

注册这个拦截器与EF运行你用你的背景前,在你的代码的任何地方如下:

DbInterception.Add(new TemporalTableCommandTreeInterceptor()); 
+0

我如何用实体框架核心做同样的事情? –

+0

@AramGevorgyan - 您可以在属性上使用属性[DatabaseGenerated(DatabaseGeneratedOption.Computed)],或者使用Fluent API方法.ValueGeneratedOnAddOrUpdate() entity.Property(e => e.ValidFrom).ValueGeneratedOnAddOrUpdate(); [见这里](http://www.learnentityframeworkcore.com/configuration/data-annotation-attributes/databasegenerated-attribute)以供参考。 –

+1

工作就像一个魅力! 'usings'如下'使用System.Data.Entity.Infrastructure.Interception; using System.Data.Entity.Core.Common.CommandTrees; using System.Data.Entity.Core.Metadata.Edm; using System.Collections.ObjectModel;' – mike123

0

另一个解决方法是创建默认约束在桌子的领域。

CREATE TABLE [dbo].[Table] (
    [Id]   INT IDENTITY(1, 1) NOT NULL, 
    [Description] NVARCHAR(100)  NOT NULL, 
    [ValidFrom]  DATETIME2(0)  GENERATED ALWAYS AS ROW START HIDDEN CONSTRAINT [Df_Table_ValidFrom] DEFAULT DATEADD(SECOND, -1, SYSUTCDATETIME()), 
    [ValidTo]  DATETIME2(0)  GENERATED ALWAYS AS ROW END HIDDEN CONSTRAINT [Df_Table_ValidTo] DEFAULT '9999.12.31 23:59:59.99', 
    PERIOD FOR SYSTEM_TIME ([ValidFrom], [ValidTo]), 
    CONSTRAINT [Pk_Table] PRIMARY KEY CLUSTERED ([Id] ASC) 
) WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [dbo].[Table_History])); 
GO 

在代码中不需要更改什么。