简体   繁体   English

实体框架不使用时态表

[英]Entity Framework not working with temporal table

I'm using database first entity framework 6. After changing some of the tables in my schema to be temporal tables, I started getting the following error when attempting to insert new data: 我正在使用数据库第一实体框架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.

It looks like EF is trying to update the values of the PERIOD columns which are managed by the system. 看起来EF正在尝试更新由系统管理的PERIOD列的值。

Removing the columns from the EDMX file seems to correct the problem, but this is not a viable solution since the columns are re-added each time the model is regenerated from the database. 从EDMX文件中删除列似乎可以解决问题,但这不是一个可行的解决方案,因为每次从数据库重新生成模型时都会重新添加列。

There are two solutions to this problem: 这个问题有两种解决方案:

  1. In the property window for the column in the EDMX designer, change the StoreGeneratedPattern on the PERIOD columns (ValidFrom and ValidTo in my case) to be identity . 在在EDMX设计师列的属性窗口,改变StoreGeneratedPatternPERIOD列(ValidFrom和ValidTo在我的情况)是identity Identity is better than computed since computed will cause EF to refresh the values on an Insert and Update as opposed to just an insert with identity 身份优于计算,因为计算将导致EF刷新插入和更新上的值,而不仅仅是具有identity的插入
  2. Create an IDbCommandTreeInterceptor implementation to remove the period columns. 创建一个IDbCommandTreeInterceptor实现以删除句点列。 This is my preferred solution since it requires no additional work when adding new tables to the model. 这是我的首选解决方案,因为在向模型添加新表时不需要额外的工作。

Here's my implementation: 这是我的实现:

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;
    }
}

Register this interceptor with EF by running the following anywhere in your code before you use your context: 在使用上下文之前,通过在代码中的任何位置运行以下命令来向EF注册此拦截器:

DbInterception.Add(new TemporalTableCommandTreeInterceptor());

An other solution is create default constraint in the fields of the table. 另一种解决方案是在表的字段中创建默认约束。

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

In the code not need alteration nothing. 在代码中不需要改动。

Making the Period start column (ValidFrom) and Period end column (ValidTo) should fix this issue. 使Period开始列(ValidFrom)和Period end列(ValidTo)应解决此问题。 We can do this by 我们可以做到这一点

ALTER TABLE [dbo].[Table1] ALTER COLUMN [ValidFrom] ADD HIDDEN;
ALTER TABLE [dbo].[Table1] ALTER COLUMN [ValidTo] ADD HIDDEN;

We can see the settings for hidden against these columns in the sys.columns table 我们可以在sys.columns表中看到隐藏这些列的设置

SELECT * FROM sys.columns WHERE is_hidden = 1

I did manage to use temporal table with entities framework without any overhead. 我确实设法使用临时表与实体框架,没有任何开销。

  1. Use the default constraint, as José Ricardo Garcia says 正如JoséRicardoGarcia所说,使用默认约束

    An other solution is create default constraint in the fields of the table. 另一种解决方案是在表的字段中创建默认约束。

    • Heres the script for altering table instead of creating table. 下面是改变表而不是创建表的脚本。

       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. Switch column to identity in edmx, as Matt Ruwe says 正如Matt Ruwe所说,将列切换为edmx中的标识

    In the property window for the column in the EDMX designer, change the StoreGeneratedPattern on the PERIOD columns (ValidFrom and ValidTo in my case) to be identity. 在EDMX设计器中列的属性窗口中,将PERIOD列(我的情况下为ValidFrom和ValidTo)上的StoreGeneratedPattern更改为标识。 Identity is better than computed since computed will cause EF to refresh the values on an Insert and Update as opposed to just an insert with identity 身份优于计算,因为计算将导致EF刷新插入和更新上的值,而不仅仅是具有身份的插入

  3. Since the two method above are working just fine for insert, they didnt work for updating the entities. 由于上面的两种方法对插入工作正常,因此它们不能用于更新实体。 I had to manually tell that the two column were not modified, 我不得不手动告诉两列没有被修改,

     Entry(existingResult).CurrentValues.SetValues(table); Entry(existingResult).Property(x => x.ValidTo).IsModified = false; Entry(existingResult).Property(x => x.ValidFrom).IsModified = false; 

now i can succesffuly call db.SaveChanges() and get rid of the error, even if the entities has been modified. 现在我可以成功调用db.SaveChanges()并消除错误,即使实体已被修改。 Hope it help! 希望它有所帮助! Note: I using DbFirst and EF6 注意:我使用的是DbFirst和EF6

I've ran into this error on a system-versioned table and I just set the EF configuration to ignore the system maintained columns like so 我在系统版本的表上遇到了这个错误,我只是将EF配置设置为忽略系统维护的列,就像这样

            Ignore(x => x.SysEndTime);
            Ignore(x => x.SysStartTime);

and insert/update works with DB still updating these columns as necessary to keep history. 插入/更新与DB一起工作,仍然根据需要更新这些列以保留历史记录。 Another way would be to setup the the column like so 另一种方法是像这样设置列

Property(x => x.SysEndTime).IsRequired().HasColumnType("datetime2").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在 Entity Framework Core 中的系统版本化临时表中查询数据 - Querying Data in a System-Versioned Temporal Table in Entity Framework Core 使用 C# 实体框架在临时表中插入记录 - Insert Record in Temporal Table using C# Entity Framework 从Entity Framework代码优先方法的时态表中获取SYSTEM_TIME信息 - Get SYSTEM_TIME information from a temporal table on Entity Framework code first method 如何在实体框架中使用系统版本的临时表? - How can I use System-Versioned Temporal Table with Entity Framework? 实体框架SQL查询字符串,从时态表中获取历史数据,并抛出数据表不兼容错误 - Entity Framework SQL query string to get history data from temporal tables throwing a data-table incompatible error 实体框架没有在没有标识列的表上工作 - entity framework not working on table without identity column 带有临时表的 Entity Framework Core 3.1 - 访问 SysStartTime 和 SysEndTime - Entity Framework Core 3.1 with Temporal Tables - Access SysStartTime and SysEndTime 实现“通用”机制来处理实体框架中的时态数据 - Implementing a “generic” mechanism to handle temporal data in Entity Framework Entity Framework Core 和 SQL Server 2016 时态表 - Entity Framework Core and SQL Server 2016 temporal tables Net Core:Entity Framework 和 SQL 服务器临时表,自动脚手架 - Net Core: Entity Framework and SQL Server Temporal Tables, Automatic Scaffolding
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM