![](/img/trans.png)
[英]Querying Data in a System-Versioned Temporal Table in Entity Framework Core
[英]Entity Framework not working with temporal table
我正在使用數據庫第一實體框架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文件中刪除列似乎可以解決問題,但這不是一個可行的解決方案,因為每次從數據庫重新生成模型時都會重新添加列。
這個問題有兩種解決方案:
StoreGeneratedPattern
在PERIOD
列(ValidFrom和ValidTo在我的情況)是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());
另一種解決方案是在表的字段中創建默認約束。
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
在代碼中不需要改動。
使Period開始列(ValidFrom)和Period end列(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
我確實設法使用臨時表與實體框架,沒有任何開銷。
正如JoséRicardoGarcia所說,使用默認約束
另一種解決方案是在表的字段中創建默認約束。
下面是改變表而不是創建表的腳本。
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
我在系統版本的表上遇到了這個錯誤,我只是將EF配置設置為忽略系統維護的列,就像這樣
Ignore(x => x.SysEndTime);
Ignore(x => x.SysStartTime);
插入/更新與DB一起工作,仍然根據需要更新這些列以保留歷史記錄。 另一種方法是像這樣設置列
Property(x => x.SysEndTime).IsRequired().HasColumnType("datetime2").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.