1

我在Temporal table使用 C# 实体框架插入数据时遇到问题

表架构是

CREATE TABLE People( 
    PeopleID int PRIMARY KEY NOT NULL, 
    Name varchar(50) Null, 
    LastName varchar(100) NULL, 
    NickName varchar(25), 
    StartTime datetime2 GENERATED ALWAYS AS ROW START NOT NULL, 
    EndTime datetime2 GENERATED ALWAYS AS ROW END NOT NULL, 
    PERIOD FOR SYSTEM_TIME (StartTime,EndTime) 
) WITH (SYSTEM_VERSIONING = ON(HISTORY_TABLE = dbo.PeopleHistory));

我创建了一个 EDMX asusal 并尝试使用以下 C# 代码插入记录

using (var db = new DevDBEntities()) {
    People1 peo = new People1() {
        PeopleID = 1,
        Name = "Emma",
        LastName = "Watson",
        NickName = "ICE"
    };

    db.Peoples.Add(peo);
    db.SaveChanges();
}

我在使用时遇到了异常db.SaveChanges()

“无法将显式值插入到表 'DevDB.dbo.People' 的 GENERATED ALWAYS 列中。将 INSERT 与列列表一起使用以排除 GENERATED ALWAYS 列,或将 DEFAULT 插入 GENERATED ALWAYS 列。”

我尝试使用 SQL Server 使用以下插入查询直接插入,它的插入很好。

INSERT INTO [dbo].[People]
           ([PeopleID]
           ,[Name]
           ,[LastName]
           ,[NickName])
     VALUES
           (2
           ,'John'
           ,'Math'
           ,'COOL')

请帮助我如何使用 C# Entity Framework 插入记录。

4

2 回答 2

8

简单总结:当 EF 尝试更新PERIOD系统版本控制列中的值时,会出现问题,而列属性值由 SQL Server 本身管理。

来自MS Docs: Temporal tables,临时表作为一对当前表和历史表工作,解释如下:

表的系统版本控制被实现为一对表,一个当前表和一个历史表。在每个表中,以下两个附加的 datetime2 列用于定义每行的有效期:

周期开始列:系统记录该列中行的开始时间,通常表示为 SysStartTime 列。

期间结束列:系统记录该列中行的结束时间,通常在 SysEndTime 列中表示。

由于StartTime&EndTime列都是自动生成的,因此必须将它们排除在任何插入或更新它们的值的尝试之外。假设您使用的是 EF 6,以下是消除错误的这些步骤:

  1. 在设计器模式下打开 EDMX 文件,将StartTime&EndTime列属性设置为Identity选项StoreGeneratedPattern。这可以防止 EF 在任何UPDATE事件上刷新值。

身份栏设置

  1. 创建一个自定义命令树拦截器类,该类实现System.Data.Entity.Infrastructure.Interception.IDbCommandTreeInterceptor并指定应设置为ReadOnlyCollection<T>(T is a DbModificationClause) 的集合子句,EF 在插入或更新修改中无法修改:

    internal class TemporalTableCommandTreeInterceptor : IDbCommandTreeInterceptor
    {
        private static ReadOnlyCollection<DbModificationClause> GenerateSetClauses(IList<DbModificationClause> modificationClauses)
        {
            var props = new List<DbModificationClause>(modificationClauses);
            props = props.Where(_ => !_ignoredColumns.Contains((((_ as DbSetClause)?.Property as DbPropertyExpression)?.Property as EdmProperty)?.Name)).ToList();
    
            var newSetClauses = new ReadOnlyCollection<DbModificationClause>(props);
            return newSetClauses;
        }
    }
    
  2. 仍然在上面的同一个类中,创建被忽略的表名列表并在 INSERT 和 UPDATE 命令中定义操作,该方法应如下所示(此方法归功于 Matt Ruwe):

    // from /a/40742144
    private static readonly List<string> _ignoredColumns = new List<string> { "StartTime", "EndTime" };
    
    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;
            }
        }
    }
    
  3. 在另一个代码部分中使用数据库上下文之前注册上面的拦截器类,方法是DbInterception

    DbInterception.Add(new TemporalTableCommandTreeInterceptor());
    

    或使用以下方法将其附加到上下文定义中DbConfigurationTypeAttribute

    public class CustomDbConfiguration : DbConfiguration
    {
        public CustomDbConfiguration()
        {
            this.AddInterceptor(new TemporalTableCommandTreeInterceptor());
        }
    }
    
    // from /a/40302086
    [DbConfigurationType(typeof(CustomDbConfiguration))]
    public partial class DataContext : System.Data.Entity.DbContext
    {
        public DataContext(string nameOrConnectionString) : base(nameOrConnectionString)
        {
            // other stuff or leave this blank
        }
    }
    

相关问题:

实体框架不使用时态表

从 IDbCommandInterceptor 的实现中获取 DbContext

仅将 IDbInterceptor 挂钩到 EntityFramework DbContext 一次

于 2017-05-30T07:22:51.620 回答
0

可能最简单的解决方案是手动编辑 .EDMX 文件并删除 StartTime 和 EndTime 列的所有痕迹。

于 2020-03-16T16:00:35.790 回答