繁体   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