【问题标题】:Insert Record in Temporal Table using C# Entity Framework使用 C# Entity Framework 在时态表中插入记录
【发布时间】:2017-05-30 05:17:19
【问题描述】:

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

表架构是

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

上遇到异常

"不能在 GENERATED ALWAYS 列中插入显式值 表'DevDB.dbo.People'。将 INSERT 与列列表一起使用以排除 GENERATED ALWAYS 列,或将 DEFAULT 插入 GENERATED ALWAYS 列。”

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

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

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

【问题讨论】:

  • 尝试在 EDMX 文件中将 StoreGeneratedPattern for PeopleID 更改为 IdentityComputed(在 GUID 的情况下)并实现 IDbCommandTreeInterceptor 以使用以下方法删除临时表中的主键列插入尝试字符串集合。
  • @TetsuyaYamamoto - 失败(即抛出相同的异常)
  • 我的错,我完全错过了 StartTimeEndTime 都是带有 GENERATED ALWAYS 的自动生成的列 - 将这些列的 StoreGeneratedPattern 设置为 Identity。当然,您需要实现额外的自定义命令树拦截器,实现 IDbCommandTreeInterceptor 来处理这些自动生成的列,可能需要比此评论更多的解释。
  • @TetsuyaYamamoto - 请您在回答部分提供完整代码帮助我。

标签: c# sql-server entity-framework entity-framework-6 temporal-database


【解决方案1】:

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

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

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

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

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

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

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

  1. 创建一个实现System.Data.Entity.Infrastructure.Interception.IDbCommandTreeInterceptor的自定义命令树拦截器类,并指定应设置为ReadOnlyCollection<T>(T是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
        }
    }
    

相关问题:

Entity Framework not working with temporal table

Getting DbContext from implementation of IDbCommandInterceptor

Hooking IDbInterceptor to EntityFramework DbContext only once

【讨论】:

  • 我怎样才能对实体框架核心做同样的事情?
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-27
    • 1970-01-01
    • 2014-01-04
    相关资源
    最近更新 更多