使用C#实体框架在时态表中插入记录

1 c# sql-server entity-framework temporal-database entity-framework-6

我在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));
Run Code Online (Sandbox Code Playgroud)

我创建了一个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();
}
Run Code Online (Sandbox Code Playgroud)

开启时我有一个例外 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')
Run Code Online (Sandbox Code Playgroud)

请协助我如何使用C#实体框架插入记录。

Tet*_*oto 5

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

MS Docs:时间表中时间表可作为当前表和历史表的一对,其解释如下:

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

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

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

由于StartTimeEndTime列都是自动生成的,因此必须将它们排除在任何试图在其上插入或更新值的操作中。假设您使用的是EF 6,以下是消除错误的步骤:

  1. 在设计模式,集两者打开EDMX文件StartTimeEndTime列属性IdentityStoreGeneratedPattern选项。这样可以防止EF在任何UPDATE事件上刷新值。

身份列设置

  1. 创建一个自定义命令树拦截器类,该类实现System.Data.Entity.Infrastructure.Interception.IDbCommandTreeInterceptor并指定应设置为ReadOnlyCollection<T>(T is a DbModificationClause)的set子句,而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;
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  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;
            }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 在数据库上下文使用之前,通过以下方法在另一个代码部分中注册上面的拦截器类DbInterception

    DbInterception.Add(new TemporalTableCommandTreeInterceptor());
    
    Run Code Online (Sandbox Code Playgroud)

    或使用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
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

相关问题:

实体框架无法与时态表一起使用

从IDbCommandInterceptor的实现中获取DbContext

仅将IDbInterceptor挂接到EntityFramework DbContext一次