我正在使用数据库第一实体框架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文件中删除列似乎可以解决问题,但这不是一个可行的解决方案,因为每次从数据库重新生成模型时都会重新添加列。
答案 0 :(得分:14)
这个问题有两种解决方案:
StoreGeneratedPattern
列上的PERIOD
(我的情况下为ValidFrom和ValidTo)更改为identity
或{{1 }}。身份可能更好,因为计算将导致EF刷新插入和更新上的值,而不是仅使用computed
的插入identity
实施以删除期间列。这是我的首选解决方案,因为在向模型添加新表时不需要额外的工作。这是我的实施:
IDbCommandTreeInterceptor
在使用上下文之前,通过在代码中的任何位置运行以下命令,将此拦截器注册到EF:
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;
}
}
答案 1 :(得分:1)
另一种解决方案是在表的字段中创建默认约束。
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
在代码中不需要改动。
答案 2 :(得分:1)
我在系统版本的表上遇到了此错误,我只是将EF配置设置为忽略系统维护的列,就像这样
Ignore(x => x.SysEndTime);
Ignore(x => x.SysStartTime);
和插入/更新与DB一起使用时,仍会根据需要更新这些列以保留历史记录。 另一种方法是像这样设置列
Property(x => x.SysEndTime).IsRequired().HasColumnType("datetime2").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
答案 3 :(得分:0)
制作“期间开始”列(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
答案 4 :(得分:0)
我确实设法在实体框架上使用了时态表,而没有任何开销。
使用默认约束,如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