Подтвердить что ты не робот

Entity Framework не работает с временной таблицей

Я использую фреймворк первой сущности базы данных 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, похоже, исправляет проблему, но это не является жизнеспособным решением, поскольку столбцы повторно добавляются каждый раз, когда модель восстанавливается из базы данных.

4b9b3361

Ответ 1

Есть два решения этой проблемы:

  1. В окне свойств для столбца в конструкторе EDMX замените StoreGeneratedPattern на столбцах PERIOD (в моем случае 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());

Ответ 2

Другое решение - создать ограничение по умолчанию в полях таблицы.

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

В коде ничего не нужно менять.

Ответ 3

Создание столбца начала периода (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

Мне удалось использовать временную таблицу со структурой сущностей без каких-либо накладных расходов.

  1. Используйте ограничение по умолчанию, как говорит Хосе Рикардо Гарсия

    Другое решение - создать ограничение по умолчанию в полях таблицы.

    • Вот сценарий изменения таблицы вместо создания таблицы.

      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. Как говорит Мэтт Руве, смените столбец на тождество в edmx

    В окне свойств для столбца в конструкторе EDMX измените StoreGeneratedPattern для столбцов PERIOD (в моем случае ValidFrom и ValidTo), чтобы они были идентичными. Удостоверение лучше, чем вычисленное, так как вычисленное заставит 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

Ответ 5

Я столкнулся с этой ошибкой в системной версии таблицы, и я просто установил конфигурацию EF, чтобы игнорировать поддерживаемые системой столбцы, например, так

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

и insert/update работает с БД, которая обновляет эти столбцы по мере необходимости для сохранения истории. Другим способом было бы настроить столбец так

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