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, кажется, исправляет проблему, но это не является жизнеспособным решением, так как столбцы добавляются повторно каждый раз, когда модель восстанавливается из базы данных.
3 ответа
Есть два решения этой проблемы:
- В окне свойств для столбца в конструкторе EDMX измените
StoreGeneratedPattern
наPERIOD
столбцы (ValidFrom и ValidTo в моем случае), чтобы быть либоidentity
или жеcomputed
, Идентичность, вероятно, лучше, так как вычисление приведет к тому, что 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());
Я столкнулся с этой ошибкой в таблице с системной версией, и я просто установил конфигурацию EF, чтобы игнорировать поддерживаемые системой столбцы, например
Ignore(x => x.SysEndTime);
Ignore(x => x.SysStartTime);
и вставка / обновление работает с БД, по-прежнему обновляя эти столбцы по мере необходимости, чтобы сохранить историю. Другой способ - настроить столбец так
Property(x => x.SysEndTime).IsRequired().HasColumnType("datetime2").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
Другое решение - создать ограничение по умолчанию в полях таблицы.
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
В коде не нужно ничего переделывать.
Мне удалось использовать темпоральную таблицу с фреймворком сущностей без каких-либо накладных расходов.
Используйте ограничение по умолчанию, как говорит Хосе Рикардо Гарсия
Другое решение - создать ограничение по умолчанию в полях таблицы.
Вот сценарий для изменения таблицы вместо создания таблицы.
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
Переключите столбец на идентификатор в edmx, как говорит Мэтт Руве
В окне свойств для столбца в конструкторе EDMX измените StoreGeneratedPattern в столбцах PERIOD (ValidFrom и ValidTo в моем случае), чтобы они были идентичными. Удостоверение лучше, чем вычисленное, поскольку вычисленное приведет к тому, что 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
Создание столбца начала периода и конца периода должно исправить эту проблему. Мы можем сделать это
ALTER TABLE [dbo].[Table1] ALTER COLUMN [ValidFrom] ADD HIDDEN;
ALTER TABLE [dbo].[Table1] ALTER COLUMN [ValidFrom] ADD HIDDEN;
Мы можем видеть настройки скрытых для этих столбцов в таблице sys.columns
SELECT * FROM sys.columns WHERE is_hidden = 1