web-dev-qa-db-ja.com

INSERT中にデータベースでDEFAULTCONSTRAINTを処理するようにEF6を取得する方法

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

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

背景

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

_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がINSERTを実行するTSQLを生成するときに、その列に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)のデフォルト値を非常に明確に提供していることに注意してください。これにより、DEFAULTCONSTRAINTが完全に克服されます。

これは、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つの理由で期待どおりに機能します。TSQLINSERTコマンドは、DEFAULT CONSTRAINTが定義されている列に値を提供しませんでした。

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

すでに試したもの

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

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

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

これは機能せず、実際にはまったく違いがないように見えます(ED:実際には機能します。ポイント2を参照)。 EFは引き続き同じTSQLを生成し、列にデフォルトを提供し、プロセスでデータベースを無効にします。

EFに「DEFAULTCONSTRAINT列を正しく処理する」ために、欠落しているフラグ、設定するのを忘れている構成項目、使用できる関数属性、作成できる継承されたクラスコードはありますか?

2。 OnModelCreating()を実行しますか?SO:OnModelCreatingは呼び出されません

Janesh(下記)は、列が_DatabaseGeneratedOption.Computed_でマークされている場合、EF will生成されたTSQLINSERTコマンドからパラメーターを削除することを示しました。どうやら私は間違った種類の接続文字列を使用していたので、それは私にとってはうまくいきませんでした(!!!)。

これが私の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 of _System.Data.SqlClient_を使用し、機能しないものは_System.Data.EntityClient_を使用します。どうやら、SqlClientプロバイダーは、OnModelCreating()メソッドの呼び出しを許可しているため、_DatabaseGeneratedOption.Computed_を使用すると効果があります。

==========未解決==========

列のDEFAULTCONSTRAINTSの目的は、値を指定できるようにする(または指定しないようにする)ことですが、データベース側では有効な値になります。 SQL Serverがこれを実行していることを知る必要はありません。また、デフォルト値が何であるか、またはあるべきかを知る必要もありません。これは完全に私の制御や知識の範囲外で起こります。

ポイントは、[オプションがあります値を指定しないことです。私はそれを提供することもできますが、提供に失敗することもあります。必要に応じて、INSERTごとに異なる方法でそれを行うことができます。

_DatabaseGeneratedOption.Computed_を使用すると、「値を常に提供できる(したがって、データベースのデフォルトメカニズムを決して使用できない)か、値を提供できない(したがって常にデータベースのデフォルトメカニズムを利用する)」。

さらに、このオプションは、デフォルトの制約のある列ではなく、実際の計算列でのみ使用することを明確に意図しています。これは、一度適用すると、モデルプロパティがINSERTおよびUPDATEの目的で実質的に読み取り専用になるためです。これが実際の計算列であるためです。うまくいくでしょう。明らかに、これはデータベースに値を提供するかしないかの私の選択の邪魔になります。

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

24
Mike Stillion

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

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

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を使用して挿入する場合は、データベースにもデフォルトの制約を追加する必要があります。

5
Colin

DatabaseGeneratedOption.Computedがinsert sqlコマンドでフィールドの送信を停止するのに役立たないことに私は強く同意しません。私は確認するために最小限の小さな例でそれを試しました、そしてそれはうまくいきました。

DatabaseGeneratedOption.Computedを任意のプロパティに適用すると、EFから任意の値を指定できるようになります。つまり、レコードの挿入または更新中に値を指定することはできません。

モデル

public class Person
{
    public int Id { get; set; }
    public int SomeId { get; set; }
    public string Name { get; set; }
}

コンテキスト

public class Context : DbContext
{
    public DbSet<Person> People { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Person>().HasKey(d => d.Id);
        modelBuilder.Entity<Person>()
            .Property(d => d.Id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        modelBuilder.Entity<Person>()
            .Property(d => d.SomeId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
    }
}

移行

public partial class Initial : DbMigration
{
    public override void Up()
    {
        CreateTable(
            "dbo.People",
            c => new
                {
                    Id = c.Int(nullable: false, identity: true),
                    SomeId = c.Int(nullable: false, defaultValue:3), //I edited it mannually to assign default value 3.
                    Name = c.String(),
                })
            .PrimaryKey(t => t.Id);

    }

    public override void Down()
    {
        DropTable("dbo.People");
    }
}

:デフォルト値3をSomeIdに手動で編集しました。

MainProgram

    static void Main(string[] args)
    {
        using (Context c = new Context())
        {
            Person p = new Person();
            p.Name = "Jenish";
            c.People.Add(p);
            c.Database.Log = Console.WriteLine;
            c.SaveChanges();
        }
    }

次のクエリがコンソールに記録されました。

Opened connection at 04/15/2015 11:32:19 AM +05:30

Started transaction at 04/15/2015 11:32:19 AM +05:30

INSERT [dbo].[People]([Name])
VALUES (@0)
SELECT [Id], [SomeId]
FROM [dbo].[People]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()


-- @0: 'Jenish' (Type = String, Size = -1)

-- Executing at 04/15/2015 11:32:20 AM +05:30

-- Completed in 3 ms with result: SqlDataReader



Committed transaction at 04/15/2015 11:32:20 AM +05:30

Closed connection at 04/15/2015 11:32:20 AM +05:30

通知SomeIdがInsertコマンドに渡されておらず、代わりにselectコマンドで選択されています。

3
Jenish Rabadiya

2013/2014年に誰かがこの質問に答えたと思います:)。プロジェクトはEF5を使用していました。彼は私に言った:

  1. .edmxを右クリックし、[プログラムから開く]、[XML(テキスト)エディター]の順に選択して、ファイルの<!-- SSDL content -->セクション内でデフォルトの制約として設定した列を見つけます。
  2. 次に、これらのフィールドの最後にStoreGeneratedPattern="Computed"を追加します。

完了したら、うまくいきました。

その後、EF6に移行しましたが、それでも機能しますが、以前に追加したものはありません。

今、私はEF6バージョン6.1.3を使用して非常に興味深い問題に直面しています。現時点では、レコードがACTIVEINACTIVEかに関係なく、ブールフィールドを1に設定するためのデフォルトの制約を持つ2つのテーブルがあります。そのような単純な。

1つのテーブルで機能します。つまり、StoreGeneratedPattern="Computed"内の列にこの.edmxを追加する必要はありません。しかし、他の表では、そうではありません。このStoreGeneratedPattern="Computed"を表1とまったく同じ列に追加する必要があります。

1
Mephisto