实体框架不使用时态表
Posted
技术标签:
【中文标题】实体框架不使用时态表【英文标题】:Entity Framework not working with temporal table 【发布时间】:2017-04-06 03:28:52 【问题描述】:我正在使用数据库优先实体框架 6。将架构中的一些表更改为临时表后,我在尝试插入新数据时开始收到以下错误:
Cannot insert an explicit value into a GENERATED ALWAYS column in table '<MyDatabase>.dbo.<MyTableName>. Use INSERT with a column list to exclude the GENERATED ALWAYS column, or insert a DEFAULT into GENERATED ALWAYS column.
看起来 EF 正在尝试更新由系统管理的 PERIOD
列的值。
从 EDMX 文件中删除列似乎可以解决问题,但这不是一个可行的解决方案,因为每次从数据库重新生成模型时都会重新添加列。
【问题讨论】:
【参考方案1】:这个问题有两种解决方案:
-
在 EDMX 设计器中列的属性窗口中,将
PERIOD
列(在我的示例中为 ValidFrom 和 ValidTo)上的 StoreGeneratedPattern
更改为 identity
。标识优于计算,因为计算将导致 EF 刷新插入和更新上的值,而不是仅使用 identity
的插入
创建一个IDbCommandTreeInterceptor
实现以删除句点列。这是我的首选解决方案,因为在向模型添加新表时不需要额外的工作。
这是我的实现:
using System.Data.Entity.Infrastructure.Interception;
using System.Data.Entity.Core.Common.CommandTrees;
using System.Data.Entity.Core.Metadata.Edm;
using System.Collections.ObjectModel;
internal class TemporalTableCommandTreeInterceptor : IDbCommandTreeInterceptor
private static readonly List<string> _namesToIgnore = new List<string> "ValidFrom", "ValidTo" ;
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;
private static ReadOnlyCollection<DbModificationClause> GenerateSetClauses(IList<DbModificationClause> modificationClauses)
var props = new List<DbModificationClause>(modificationClauses);
props = props.Where(_ => !_namesToIgnore.Contains((((_ as DbSetClause)?.Property as DbPropertyExpression)?.Property as EdmProperty)?.Name)).ToList();
var newSetClauses = new ReadOnlyCollection<DbModificationClause>(props);
return newSetClauses;
在使用上下文之前,通过在代码中的任何位置运行以下命令,向 EF 注册此拦截器:
DbInterception.Add(new TemporalTableCommandTreeInterceptor());
【讨论】:
我怎样才能对实体框架核心做同样的事情? @AramGevorgyan - 您可以在属性上使用属性 [DatabaseGenerated(DatabaseGeneratedOption.Computed)] 或使用 Fluent API 方法 .ValueGeneratedOnAddOrUpdate() 例如entity.Property(e => e.ValidFrom).ValueGeneratedOnAddOrUpdate(); see here供参考。 工作就像一个魅力!usings
如下using System.Data.Entity.Infrastructure.Interception; using System.Data.Entity.Core.Common.CommandTrees; using System.Data.Entity.Core.Metadata.Edm; using System.Collections.ObjectModel;
【参考方案2】:
我在系统版本表上遇到了这个错误,我只是将 EF 配置设置为忽略系统维护的列,就像这样
Ignore(x => x.SysEndTime);
Ignore(x => x.SysStartTime);
并且插入/更新与数据库一起工作,仍然根据需要更新这些列以保留历史记录。 另一种方法是像这样设置列
Property(x => x.SysEndTime).IsRequired().HasColumnType("datetime2").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
【讨论】:
对我来说完美的解决方案!完美运行。 数据模型中的[DatabaseGenerated(DatabaseGeneratedOption.Computed)] public DateTime? SysStartTime get; set;
会不会有同样的效果?见docs.microsoft.com/en-us/ef/core/modeling/…【参考方案3】:
另一种解决方案是在表的字段中创建默认约束。
CREATE TABLE [dbo].[Table] (
[Id] INT IDENTITY(1, 1) NOT NULL,
[Description] NVARCHAR(100) NOT NULL,
[ValidFrom] DATETIME2(0) GENERATED ALWAYS AS ROW START HIDDEN CONSTRAINT [Df_Table_ValidFrom] DEFAULT DATEADD(SECOND, -1, SYSUTCDATETIME()),
[ValidTo] DATETIME2(0) GENERATED ALWAYS AS ROW END HIDDEN CONSTRAINT [Df_Table_ValidTo] DEFAULT '9999.12.31 23:59:59.99',
PERIOD FOR SYSTEM_TIME ([ValidFrom], [ValidTo]),
CONSTRAINT [Pk_Table] PRIMARY KEY CLUSTERED ([Id] ASC)
) WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [dbo].[Table_History]));
GO
在代码中不需要改动什么。
【讨论】:
创建了这个表,但是插入了 db.Tables.Add(new Table() Description = "des", Id = 1);仍然给出错误无法将显式值插入... 即使使用默认约束? 好吧,我刚刚发布了我的答案,它可以用于插入,但不能更新。您的回答是我解决方案的一部分,谢谢!【参考方案4】:我确实设法在没有任何开销的情况下将时态表与实体框架一起使用。
正如 José Ricardo Garcia 所说,使用默认约束
另一种解决方案是在表的字段中创建默认约束。
这是更改表格而不是创建表格的脚本。
ALTER TABLE [dbo].[Table]
ADD ValidFrom DATETIME2(0) GENERATED ALWAYS AS ROW START HIDDEN CONSTRAINT [Df_Table_ValidFrom] DEFAULT DATEADD(SECOND, -1, SYSUTCDATETIME()),
ValidTo DATETIME2(0) GENERATED ALWAYS AS ROW END HIDDEN CONSTRAINT [Df_Table_ValidTo] DEFAULT '9999.12.31 23:59:59.99',
PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo);
go
ALTER TABLE [dbo].[Table]
SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE=dbo.[TableHistory]))
GO
正如 Matt Ruwe 所说,在 edmx 中将列切换为身份
在 EDMX 设计器中列的属性窗口中,将 PERIOD 列(在我的例子中为 ValidFrom 和 ValidTo)上的 StoreGeneratedPattern 更改为标识。标识比计算好,因为计算将导致 EF 刷新插入和更新上的值,而不是仅使用标识的插入
由于上述两种方法在插入时工作得很好,它们对更新实体不起作用。我不得不手动告诉这两列没有被修改,
Entry(existingResult).CurrentValues.SetValues(table);
Entry(existingResult).Property(x => x.ValidTo).IsModified = false;
Entry(existingResult).Property(x => x.ValidFrom).IsModified = false;
现在我可以成功调用db.SaveChanges()
并摆脱错误,即使实体已被修改。希望对您有所帮助!
注意:我使用 DbFirst 和 EF6
【讨论】:
【参考方案5】:制作期间开始列 (ValidFrom) 和期间结束列 (ValidTo) 应该可以解决此问题。我们可以这样做
ALTER TABLE [dbo].[Table1] ALTER COLUMN [ValidFrom] ADD HIDDEN;
ALTER TABLE [dbo].[Table1] ALTER COLUMN [ValidTo] ADD HIDDEN;
我们可以在 sys.columns 表中看到隐藏这些列的设置
SELECT * FROM sys.columns WHERE is_hidden = 1
【讨论】:
以上是关于实体框架不使用时态表的主要内容,如果未能解决你的问题,请参考以下文章
Net Core:实体框架和 SQL Server 时态表、自动脚手架