簡體   English   中英

實體框架不使用時態表

[英]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文件中刪除列似乎可以解決問題,但這不是一個可行的解決方案,因為每次從數據庫重新生成模型時都會重新添加列。

這個問題有兩種解決方案:

  1. 在在EDMX設計師列的屬性窗口,改變StoreGeneratedPatternPERIOD列(ValidFrom和ValidTo在我的情況)是identity 身份優於計算,因為計算將導致EF刷新插入和更新上的值,而不僅僅是具有identity的插入
  2. 創建一個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

我確實設法使用臨時表與實體框架,沒有任何開銷。

  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 
  2. 正如Matt Ruwe所說,將列切換為edmx中的標識

    在EDMX設計器中列的屬性窗口中,將PERIOD列(我的情況下為ValidFrom和ValidTo)上的StoreGeneratedPattern更改為標識。 身份優於計算,因為計算將導致EF刷新插入和更新上的值,而不僅僅是具有身份的插入

  3. 由於上面的兩種方法對插入工作正常,因此它們不能用於更新實體。 我不得不手動告訴兩列沒有被修改,

     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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM