データベースファーストエンティティフレームワーク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ファイルから列を削除すると問題が修正されるようですが、モデルがデータベースから再生成されるたびに列が再追加されるため、これは実行可能な解決策ではありません。
この問題には2つの解決策があります。
StoreGeneratedPattern
列(私の場合はValidFromとValidTo)のPERIOD
をidentity
に変更します。 identity
を使用した単なる挿入ではなく、計算によってEFが挿入と更新の値を更新するため、IDは計算よりも優れています。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);
挿入/更新はDBで機能し、履歴を保持するために必要に応じてこれらの列を更新します。別の方法は、次のように列を設定することです。
Property(x => x.SysEndTime).IsRequired().HasColumnType("datetime2").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
もう1つの解決策は、テーブルのフィールドにデフォルトの制約を作成することです。
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
コードでは何も変更する必要はありません。
オーバーヘッドなしで、エンティティフレームワークで一時テーブルを使用することができました。
JoséRicardoGarciaが言うように、デフォルトの制約を使用します
もう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
_
Matt Ruweが言うように、edmxで列をIDに切り替えます
EDMXデザイナの列のプロパティウィンドウで、PERIOD列(私の場合はValidFromとValidTo)のStoreGeneratedPatternをidentityに変更します。 IDのある挿入だけではなく、計算によってEFが挿入と更新の値を更新するため、IDは計算よりも優れています。
上記の2つの方法は挿入には問題なく機能しているため、エンティティの更新には機能しませんでした。 2つの列が変更されていないことを手動で伝える必要がありました。
_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を使用しています
期間開始列(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