使用C#Entity Framework在时态表中插入记录

时间:2017-05-30 05:17:19

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

我在使用C#Entity Framework 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#Code

插入记录
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与列列表一起使用以排除   生成ALWAYS列,或将DEFAULT插入GENERATED ALWAYS   。柱"

我尝试使用以下插入查询使用SQL Server直接插入,插入正常。

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

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

2 个答案:

答案 0 :(得分:4)

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

MS Docs: Temporal tables开始,时态表作为一对当前表和历史表工作,如下所示:

  

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

     

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

     

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

同时StartTime& EndTime列是自动生成的,必须将其排除在任何插入或更新值的尝试之外。假设您使用的是EF 6:

,以下是摆脱错误的步骤
  1. 在设计师模式下打开EDMX文件,同时设置StartTime& EndTime选项中的Identity列属性为StoreGeneratedPattern。这可以防止EF刷新任何UPDATE事件的值。
  2. Identity Column Setting

    1. 创建一个自定义命令树拦截器类,它实现System.Data.Entity.Infrastructure.Interception.IDbCommandTreeInterceptor并指定set子句,这些子句应设置为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
          }
      }
      
    4. 相关问题:

      Entity Framework not working with temporal table

      Getting DbContext from implementation of IDbCommandInterceptor

      Hooking IDbInterceptor to EntityFramework DbContext only once

答案 1 :(得分:0)

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

相关问题