26

私は EF (最初の 1 週間) は初めてですが、データベースやプログラミングは初めてではありません。他の人も同様の質問をしていますが、説明が必要なほど適切な詳細で質問されたり、説明されたりしているとは思わないので、ここに行きます.

質問: INSERT の実行時に DEFAULT CONSTRAINT が定義されているデータベース内の列を Entity Framework で適切に処理するにはどうすればよいですか? つまり、挿入操作中にモデルに値を指定しない場合、データベース定義の DEFAULT CONSTRAINT が機能するように、生成された TSQL INSERT コマンドからその列を EF に除外させるにはどうすればよいでしょうか?

バックグラウンド

Entity Framework 6 (EF6) と、SQL Server が更新できる列との相互作用をテストするためだけに、作成した単純なテーブルがあります。これは、IDENTITY、TIMESTAMP、COMPUTED、および DEFAULT CONSTRAINT が適用されたいくつかの列を利用します。

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[DBUpdateTest](
    [RowID] [int] IDENTITY(200,1) NOT NULL,
    [UserValue] [int] NOT NULL,
    [DefValue1] [int] NOT NULL,
    [DefValue2null] [int] NULL,
    [DefSecond] [int] NOT NULL,
    [CalcValue]  AS 
        (((([rowid]+[uservalue])+[defvalue1])+[defvalue2null])*[defsecond]),
    [RowTimestamp] [timestamp] NULL,
    CONSTRAINT [PK_DBUpdateTest] PRIMARY KEY CLUSTERED 
    (
        [RowID] ASC
    )
    WITH 
    (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF,
    ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) 
) 
GO
ALTER TABLE [dbo].[DBUpdateTest] 
ADD CONSTRAINT [DF_DBUpdateTest_DefValue1]      
DEFAULT ((200)) FOR [DefValue1]
GO
ALTER TABLE [dbo].[DBUpdateTest] 
ADD CONSTRAINT [DF_DBUpdateTest_DefValue2null]  
DEFAULT ((30)) FOR [DefValue2null]
GO
ALTER TABLE [dbo].[DBUpdateTest] 
ADD  CONSTRAINT [DF_DBUpdateTest_DefSecond]  
DEFAULT (datepart(second,getdate())) FOR [DefSecond]
GO

EF6 は、IDENTITY、TIMESTAMP、および COMPUTED 列を完全に処理します。つまり、INSERT または UPDATE (経由context.SaveChanges()) の後、EF は新しい値をエンティティ オブジェクトに読み込んで、すぐに使用できるようにします。

ただし、これは DEFAULT CONSTRAINT を持つ列では発生しません。そして、私が知る限り、これは、EF が TSQL を生成して INSERT を実行するときに、その列に DEFAULT CONSTRAINT が定義されていないかのように、null 許容型または null 非許容型に共通の既定値を提供するためです。したがって、EF が DEFAULT CONSTRAINT の可能性を完全に無視していることは明らかです。

DBUpdateTest レコードを挿入する EF コードを次に示します (単一の列のみを更新します)。

DBUpdateTest myVal = new DBUpdateTest();
myVal.UserValue = RND.Next(20, 90);
DB.DBUpdateTests.Add(myVal);
DB.SaveChanges();

これは、DBUpdateTest への INSERT 中に EF によって生成された SQL です (可能なすべての列を忠実に更新します)。

 exec sp_executesql 
 N'INSERT [dbo].[DBUpdateTest]([UserValue], [DefValue1], [DefValue2null],
          [DefSecond])
   VALUES (@0, @1, NULL, @2)
   SELECT [RowID], [CalcValue], [RowTimestamp]
   FROM [dbo].[DBUpdateTest]
   WHERE @@ROWCOUNT > 0 AND [RowID] = scope_identity()',
 N'@0 int,@1 int,@2 int',@0=86,@1=0,@2=54

INT NOT NULL (0) とINT NULL (null)のデフォルト値を非常に明確に提供していることに注意してください。これにより、DEFAULT CONSTRAINT が完全に克服されます。

これは、EF INSERT コマンドが実行されたときに起こることであり、null 許容列に NULL を指定し、INT 列に ZERO を指定します。

RowID   UserValue   DefValue1   DefValue2null   DefSecond   CalcValue
=========================================================================
211     100         200         NULL            0           NULL

一方、次のステートメントを実行すると:

insert into DBUpdateTest (UserValue) values (100)

私はそのようなレコードを取得します

RowID   UserValue   DefValue1   DefValue2null   DefSecond   CalcValue
=========================================================================
211     100         200         30              7           3787

これが期待どおりに機能する理由の 1 つは、TSQL INSERT コマンドが定義済みの DEFAULT CONSTRAINT を持つ列の値を提供しなかったことです。

したがって、私がやろうとしているのは、モデル オブジェクトで明示的に値を設定しない場合、EF に DEFAULT CONSTRAINT 列を INSERT TSQL から除外させることです。

すでに試したこと

1. デフォルトの制約を認識していますか? SO: EF にデフォルトの制約を処理させる方法

私のクラスのOnModelCreating()メソッドではDbContext、DEFAULT CONSTRAINT を持つ列が COMPUTED フィールドであることを EF に伝えることができることが推奨されましたが、そうではありません。ただし、INSERTの後にEFが少なくとも値を読み戻すかどうかを確認したかったのです(それによって、その列に値を割り当てることができなくなる可能性があることも気にしないでください。ほしくない):

        modelBuilder.Entity<DBUpdateTest>()
            .Property(e => e.DefValue1)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);

これは機能せず、実際には何も変わらないように見えます ( ED: 実際には機能します。ポイント 2 を参照してください)。EF は引き続き同じ TSQL を生成し、列の既定値を提供し、その過程でデータベースを無効にします。

不足しているフラグ、設定するのを忘れている構成項目、使用できる関数属性、作成できる継承されたクラス コードはありますか?

2. OnModelCreating() を取得して実行しますか? SO: OnModelCreating が呼び出されない

Janesh (以下) は、列が でマークされている場合、EF生成された TSQL INSERT コマンドからパラメーターを削除することを示しましたDatabaseGeneratedOption.Computed。明らかに間違った種類の接続文字列を使用していたため、うまくいきませんでした(!!!)。

これが私の App.config で<connectionStrings>、「悪い」接続文字列と「良い」接続文字列を示すセクションは次のとおりです。

<connectionStrings>
  <add name="TEST_EF6Entities_NO_WORKY" providerName="System.Data.EntityClient" connectionString="metadata=res://*/TCXModel.csdl|res://*/TCXModel.ssdl|res://*/TCXModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=...ConnectStringHere...;App=EntityFramework&quot;"  />
  <add name="TEST_EF6Entities_IT_WORKS" providerName="System.Data.SqlClient" connectionString="data source=...ConnectStringHere...;App=EntityFramework;"  />
</connectionStrings>

違い: 機能するものはのProviderNameを使用System.Data.SqlClientし、機能しないものは を使用しSystem.Data.EntityClientます。どうやら SqlClient プロバイダーはOnModelCreating()メソッドを呼び出すことを許可しているようです。これにより、私の使用がDatabaseGeneratedOption.Computed効果を発揮します。

========== まだ解決されていません ==========

列に対する DEFAULT CONSTRAINTS の目的は、値を提供する (または提供しない) ことを許可し、それでもデータベース側で有効な値になるようにすることです。SQL Server がこれを行っていることを知る必要はありません。また、既定値が何であるか、またはそうあるべきかを知る必要もありません。これは、私の制御または知識の範囲外で完全に発生します。

ポイントは、値を指定しないという選択肢があるということです。必要に応じて、INSERT ごとに別の方法で提供することもできます。

この場合、使用DatabaseGeneratedOption.Computedは実際には有効なオプションではありません。これは、「常に値を提供できる (したがって、データベースの既定のメカニズムを決して利用しない) か、値を決して提供できない (したがって、常にデータベースの既定のメカニズムを利用する)」という選択を迫られるためです。 )」。

さらに、このオプションは、DEFAULT CONSTRAINT を持つ列ではなく、実際の計算列でのみ使用することを明確に意図しています。これは、モデル プロパティが適用されると、INSERT および UPDATE の目的で実質的に読み取り専用になるためです。動作します。明らかに、これは、データベースに値を提供するかどうかの私の選択の妨げになります。

それで、私はまだ尋ねます:DEFAULT CONSTRAINTが定義されているデータベース列でEFを「正しく」動作させるにはどうすればよいですか?

4

3 に答える 3

5

このビットはあなたの質問への鍵です:

したがって、私がやろうとしているのは、オブジェクトに値を明示的に設定しない場合、EF NOT に DEFAULT CONSTRAINT 列を INSERT TSQL に含めることです。

Entity Framework はそれを行いません。フィールドは、常に計​​算されるか、常に挿入および更新に含まれます。ただし、説明した方法で動作するクラスを作成できます。コンストラクターで、またはバッキング フィールドを使用して、フィールドを (明示的に) デフォルト値に設定する必要があります。

public class DBUpdateTest
/* public partial class DBUpdateTest*/ //version for database first
{
   private _DefValue1 = 200;
   private _DefValue2 = 30;

   public DbUpdateTest()
   {
      DefSecond = DateTime.Second;
   }

   public DefSecond { get; set; }

   public DefValue1
   {
      get { return _DefValue1; }
      set { _DefValue1 = value; }
   }

   public DefValue2
   {
      get { return _DefValue2; }
      set { _DefValue2 = value; }
   }
}

常にこれらのクラスを使用して挿入する場合は、おそらくデータベースにデフォルトを設定する必要はありませんが、他の場所から sql を使用して挿入する場合は、データベースにもデフォルトの制約を追加する必要があります。

于 2015-04-15T08:28:04.983 に答える