使用 C# Entity Framework 在临时 Table 中插入记录
Insert Record in Temporal Table using C# Entity Framework
我在使用 C# Entity Framework
在 Temporal table
中插入数据时遇到问题
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,并尝试使用以下 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()
时遇到异常
"Cannot insert an explicit value into a GENERATED ALWAYS column in
table 'DevDB.dbo.People'. Use INSERT with a column list to exclude the
GENERATED ALWAYS column, or insert a DEFAULT into GENERATED ALWAYS
column."
我尝试使用 SQL 服务器使用以下插入查询直接插入,它的插入很好。
INSERT INTO [dbo].[People]
([PeopleID]
,[Name]
,[LastName]
,[NickName])
VALUES
(2
,'John'
,'Math'
,'COOL')
请帮助我如何使用 C# 插入记录 Entity Framework。
简明摘要:当 EF 尝试更新 PERIOD
系统版本控制列中的值时出现问题,其中列 属性 值由 SQL 服务器本身。
从 MS Docs: Temporal tables 开始,时间 table 作为一对当前 table 和历史 table 解释如下:
System-versioning for a table is implemented as a pair of tables, a
current table and a history table. Within each of these tables, the
following two additional datetime2 columns are used to define the
period of validity for each row:
Period start column: The system records the start time for the row in this column, typically denoted as the SysStartTime column.
Period end column: The system records the end time for the row in this column, typically denoted at the SysEndTime column.
由于 StartTime
和 EndTime
列都是自动生成的,因此必须排除对它们插入或更新值的任何尝试。假设您使用的是 EF 6,以下是消除错误的步骤:
- 在设计器模式下打开 EDMX 文件,在
StoreGeneratedPattern
选项中将 StartTime
和 EndTime
列属性设置为 Identity
。这可以防止 EF 在任何 UPDATE
事件上刷新值。
创建自定义命令树拦截器class实现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;
}
}
仍然在上面的 class 中,创建被忽略的 table 名称列表并在 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;
}
}
}
在另一个代码部分使用数据库上下文之前,通过使用 DbInterception
:
注册上面的拦截器 class
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
}
}
相关问题:
Getting DbContext from implementation of IDbCommandInterceptor
可能最简单的解决方案是手动编辑 .EDMX 文件并删除 StartTime 和 EndTime 列的所有痕迹。
我在使用 C# Entity Framework
在Temporal table
中插入数据时遇到问题
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,并尝试使用以下 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()
"Cannot insert an explicit value into a GENERATED ALWAYS column in table 'DevDB.dbo.People'. Use INSERT with a column list to exclude the GENERATED ALWAYS column, or insert a DEFAULT into GENERATED ALWAYS column."
我尝试使用 SQL 服务器使用以下插入查询直接插入,它的插入很好。
INSERT INTO [dbo].[People]
([PeopleID]
,[Name]
,[LastName]
,[NickName])
VALUES
(2
,'John'
,'Math'
,'COOL')
请帮助我如何使用 C# 插入记录 Entity Framework。
简明摘要:当 EF 尝试更新 PERIOD
系统版本控制列中的值时出现问题,其中列 属性 值由 SQL 服务器本身。
从 MS Docs: Temporal tables 开始,时间 table 作为一对当前 table 和历史 table 解释如下:
System-versioning for a table is implemented as a pair of tables, a current table and a history table. Within each of these tables, the following two additional datetime2 columns are used to define the period of validity for each row:
Period start column: The system records the start time for the row in this column, typically denoted as the SysStartTime column.
Period end column: The system records the end time for the row in this column, typically denoted at the SysEndTime column.
由于 StartTime
和 EndTime
列都是自动生成的,因此必须排除对它们插入或更新值的任何尝试。假设您使用的是 EF 6,以下是消除错误的步骤:
- 在设计器模式下打开 EDMX 文件,在
StoreGeneratedPattern
选项中将StartTime
和EndTime
列属性设置为Identity
。这可以防止 EF 在任何UPDATE
事件上刷新值。
创建自定义命令树拦截器class实现
System.Data.Entity.Infrastructure.Interception.IDbCommandTreeInterceptor
并指定设置子句应设置为ReadOnlyCollection<T>
(T is aDbModificationClause
),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; } }
仍然在上面的 class 中,创建被忽略的 table 名称列表并在 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; } } }
在另一个代码部分使用数据库上下文之前,通过使用
注册上面的拦截器 classDbInterception
: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 } }
相关问题:
Getting DbContext from implementation of IDbCommandInterceptor
可能最简单的解决方案是手动编辑 .EDMX 文件并删除 StartTime 和 EndTime 列的所有痕迹。