Entity Frameworkが子オブジェクトを保存/挿入しようとするのを防ぐにはどうすればよいですか?


100

エンティティフレームワークでエンティティを保存するとき、当然、指定されたエンティティのみを保存しようとするものだと思いました。ただし、そのエンティティの子エンティティも保存しようとしています。これは、あらゆる種類の整合性の問題を引き起こしています。保存するエンティティのみを保存し、すべての子オブジェクトを無視するようにEFに強制するにはどうすればよいですか?

プロパティを手動でnullに設定すると、「操作に失敗しました。1つ以上の外部キープロパティがnullにできないため、関係を変更できませんでした。」というエラーが表示されます。EFが子オブジェクトをそのままにしておいたので、子オブジェクトを特にnullに設定しているため、これは非常に逆効果です。

なぜ子オブジェクトを保存/挿入したくないのですか?

これはコメントで前後に議論されているので、子オブジェクトをそのままにしておく理由を説明します。

私が作成しているアプリケーションでは、EFオブジェクトモデルはデータベースから読み込まれていませんが、フラットファイルの解析中にデータオブジェクトとして使用されています。子オブジェクトの場合、これらの多くは、親テーブルのさまざまなプロパティを定義するルックアップテーブルを参照します。たとえば、プライマリエンティティの地理的な場所です。

私はこれらのオブジェクトを自分で作成したので、EFはこれらが新しいオブジェクトであると想定し、親オブジェクトと共に挿入する必要があります。ただし、これらの定義はすでに存在しており、データベースに重複を作成したくありません。私は、EFオブジェクトを使用して、ルックアップを実行し、メインテーブルエンティティに外部キーを設定しています。

実際のデータである子オブジェクトがあったとしても、最初に親を保存し、主キーまたはEFを取得する必要があるだけで、混乱を招くようです。これがいくつかの説明を与えることを願っています。


私が知る限り、子オブジェクトをnullにする必要があります。
ヨハン14

こんにちは、ヨハン。動作しません。コレクションをnullにすると、エラーがスローされます。方法によっては、キーがnullである、またはコレクションが変更されたというメッセージが表示されます。明らかに、これらのことは真実ですが、私は意図的にそれを行ったので、触れるべきではないオブジェクトをそのままにしておきます。
マークミカレフ2014

陶酔感、それは全く役に立たない。
マークミカレフ2014

@Euphoric子オブジェクトを変更しない場合でも、EFはデフォルトでそれらを挿入しようとし、それらを無視したり更新したりしません。
ヨハン

私を本当に困らせているのは、それらのオブジェクトを実際に無効にするために自分の道を行くと、それらを放っておいてほしいと気づくのではなく、不平を言うということです。これらの子オブジェクトはすべてオプション(データベースではnull可能)であるため、EFにそれらのオブジェクトがあったことを忘れさせる方法はありますか?つまり、そのコンテキストまたはキャッシュを何とか消去しますか?
マークミカレフ2014

回答:


55

私の知る限り、2つの選択肢があります。

オプション1)

すべての子オブジェクトをnullにします。これにより、EFが何も追加しないようになります。また、データベースから何も削除されません。

オプション2)

次のコードを使用して、コンテキストから切り離された子オブジェクトを設定します

 context.Entry(yourObject).State = EntityState.Detached

List/ は切り離せないことに注意してくださいCollection。リストをループして、リストの各アイテムを切り離す必要があります

foreach (var item in properties)
{
     db.Entry(item).State = EntityState.Detached;
}

こんにちは、ヨハン。コレクションの1つを切り離そうとしたところ、次のエラーが発生しました。エンティティタイプHashSet`1は、現在のコンテキストのモデルの一部ではありません。
マークミカレフ2014

@MarkyMarkコレクションを切り離さないでください。コレクションをループして、オブジェクトのオブジェクトを切り離す必要があります(今すぐ回答を更新します)。
ヨハン

11
残念ながら、すべてを切り離すためのループのリストがあっても、EFはまだいくつかの関連テーブルに挿入しようとしているようです。この時点で、EFを完全に削除し、少なくとも適切に動作するSQLに切り替える準備ができています。なんて痛みだ。
マークミカレフ2014

2
追加する前にcontext.Entry(yourObject).Stateを使用できますか?
Thomas

1
@ mirind4状態は使用しませんでした。オブジェクトを挿入する場合、すべての子がnullであることを確認します。更新時には、子なしでオブジェクトを最初に取得します。
Thomas

41

要するに、外部キーを使用すれば、1日が節約できます。

学校があるとしますエンティティを、これは市が市に属し、多くの学校があり、学校の多対1の関係です。また、都市がルックアップテーブルに既に存在しているため、新しい学校を挿入するときにそれらの都市が再度挿入されないようにする必要があります。

最初は次のようにエンティティを定義します。

public class City
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class School
{
    public int Id { get; set; }
    public string Name { get; set; }

    [Required]
    public City City { get; set; }
}

そして、次のように学校の挿入を行うことができます(newItemに割り当てられたCityプロパティがすでにあると仮定します):

public School Insert(School newItem)
{
    using (var context = new DatabaseContext())
    {
        context.Set<School>().Add(newItem);
        // use the following statement so that City won't be inserted
        context.Entry(newItem.City).State = EntityState.Unchanged;
        context.SaveChanges();
        return newItem;
    }
}

この場合、上記のアプローチは完全に機能する可能性がありますが、私には、より明確で柔軟性のある外部キーアプローチをお勧めします。以下の更新されたソリューションを参照してください。

public class City
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class School
{
    public int Id { get; set; }
    public string Name { get; set; }

    [ForeignKey("City_Id")]
    public City City { get; set; }

    [Required]
    public int City_Id { get; set; }
}

このようにして、学校に外部キーCity_Idがあり、Cityエンティティを参照することを明示的に定義します。したがって、Schoolの挿入に関しては、次のことができます。

    public School Insert(School newItem, int cityId)
    {
        if(cityId <= 0)
        {
            throw new Exception("City ID no provided");
        }

        newItem.City = null;
        newItem.City_Id = cityId;

        using (var context = new DatabaseContext())
        {
            context.Set<School>().Add(newItem);
            context.SaveChanges();
            return newItem;
        }
    }

この場合は、明示的に指定CITY_ID新しいレコードのをと削除市を EFが一緒にコンテキストに追加する気にしないようにグラフから学校

最初の印象では、外部キーのアプローチはより複雑に見えますが、この考え方により、多対多の関係を挿入するときに多くの時間を節約できます(学校と学生の関係があり、学生がCityプロパティを持っているなど)。

これがあなたのお役に立てば幸いです。


すばらしい答え、私はたくさん助けてくれました!しかし、[Range(1, int.MaxValue)]属性を使用してCity_Idの最小値1を記述する方が良いでしょうか?
Dan Rayson 2017年

夢のような!!どうもありがとう!
CJH 2018

これにより、呼び出し元のSchoolオブジェクトから値が削除されます。SaveChangesはnullナビゲーションプロパティの値をリロードしますか?それ以外の場合、情報が必要な場合、呼び出し側はInsert()メソッドを呼び出した後にCityオブジェクトを再ロードする必要があります。これは私が頻繁に使用したパターンですが、誰かが良いパターンを持っている場合、私はまだより良いパターンを受け入れています。
偏った

1
これは本当に役に立ちました。EntityState.Unchagedは、単一のトランザクションとして保存していた大きなオブジェクトグラフで、ルックアップテーブルの外部キーを表すオブジェクトを割り当てるために必要なものです。EF Coreは、直感的ではないエラーメッセージIMOをスローします。パフォーマンス上の理由でめったに変更されないルックアップテーブルをキャッシュしました。これは、ルックアップテーブルオブジェクトのPKがデータベースにあるものと同じであるため、変更されていないことがわかっていて、FKを既存のアイテムに割り当てるだけであることが前提です。代わりに、ルックアップテーブルオブジェクトを新規として挿入しようとします。
tnk479

nullを設定したり、状態を変更せずに設定したりする組み合わせは、私にとってはうまくいきません。EFは、親のスカラーフィールドを更新するだけの場合、新しい子レコードを挿入する必要があると主張しています。
BobRz

21

あなただけの店に変更したい場合は、親オブジェクトとそののいずれかに避ける保存の変更オブジェクト、なぜただ、次の操作を行いません。

using (var ctx = new MyContext())
{
    ctx.Parents.Attach(parent);
    ctx.Entry(parent).State = EntityState.Added;  // or EntityState.Modified
    ctx.SaveChanges();
}

1行目は、親オブジェクトとそれに依存する子オブジェクトのグラフ全体を、Unchanged状態のコンテキストに関連付けます。

2行目は、親オブジェクトの状態のみを変更し、子はUnchanged状態のままにします。

新しく作成したコンテキストを使用しているので、データベースへの他の変更が保存されないように注意してください。


2
この回答には、誰かが後で来て子オブジェクトを追加しても、既存のコードが壊れないという利点があります。これは、他のユーザーが子オブジェクトを明示的に除外することを要求する「オプトイン」ソリューションです。
ジム、

これはコアでは動作しません。「アタッチ:到達可能なエンティティにストア生成のキーがあり、キー値が割り当てられていない場合を除いて、すべての到達可能なエンティティをアタッチします。これらには追加済みのマークが付けられます。」子供も新しい場合は追加されます。
mmix

14

推奨されるソリューションの1つは、同じデータベースコンテキストからナビゲーションプロパティを割り当てることです。このソリューションでは、データベースコンテキストの外部から割り当てられたナビゲーションプロパティが置き換えられます。次の例を参照してください。

class Company{
    public int Id{get;set;}
    public Virtual Department department{get; set;}
}
class Department{
    public int Id{get; set;}
    public String Name{get; set;}
}

データベースに保存:

 Company company = new Company();
 company.department = new Department(){Id = 45}; 
 //an Department object with Id = 45 exists in database.    

 using(CompanyContext db = new CompanyContext()){
      Department department = db.Departments.Find(company.department.Id);
      company.department = department;
      db.Companies.Add(company);
      db.SaveChanges();
  }

Microsoftはこれを機能として採用していますが、これは不愉快です。会社オブジェクトに関連付けられている部門オブジェクトにデータベースに既に存在するIDがある場合、EFは会社オブジェクトをデータベースオブジェクトに関連付けるだけではないのはなぜですか?なぜ自分たちで協会を管理する必要があるのでしょうか。新しいオブジェクトの追加中にナビゲーションプロパティを処理することは、データベース操作をSQLからC#に移動するようなものであり、開発者にとって煩雑です。


IDが提供されたときにEFが新しいレコードを作成しようとするのはばかげていることに100%同意します。選択リストのオプションを実際に使用するオブジェクトで埋める方法があった場合。
T3.0

10

まず、EFでエンティティを更新するには2つの方法があることを知っておく必要があります。

  • アタッチされたオブジェクト

上記のいずれかの方法を使用してオブジェクトコンテキストにアタッチされたオブジェクトの関係を変更する場合、Entity Frameworkは外部キー、参照、およびコレクションの同期を維持する必要があります。

  • 切断されたオブジェクト

切断されたオブジェクトを使用している場合は、手動で同期を管理する必要があります。

私が作成しているアプリケーションでは、EFオブジェクトモデルはデータベースから読み込まれていませんが、フラットファイルの解析中にデータオブジェクトとして使用されています。

つまり、切断されたオブジェクトを使用していますが、独立した関連付けを使用しているか、外部キーの関連付けを使用ているかは不明です。

  • 追加

    既存の子オブジェクト(データベースに存在するオブジェクト)を持つ新しいエンティティを追加するときに、子オブジェクトがEFによって追跡されない場合、子オブジェクトは再挿入されます。最初に子オブジェクトを手動でアタッチしない限り。

      db.Entity(entity.ChildObject).State = EntityState.Modified;
      db.Entity(entity).State = EntityState.Added;
  • 更新

    エンティティを変更済みとしてマークするだけで、すべてのスカラープロパティが更新され、ナビゲーションプロパティは単に無視されます。

      db.Entity(entity).State = EntityState.Modified;

グラフ差分

切断されたオブジェクトを操作するときにコードを簡略化したい場合は、diffライブラリグラフ化してみてください。

はじめに、エンティティフレームワークコードのGraphDiffの紹介-分離されたエンティティのグラフの自動更新を許可します

サンプルコード

  • エンティティが存在しない場合は挿入し、存在しない場合は更新します。

      db.UpdateGraph(entity);
  • それは、そうでない場合は更新が存在しない場合には、企業挿入、それが存在し、それ以外の場合は更新しない場合は、子オブジェクトの挿入。

      db.UpdateGraph(entity, map => map.OwnedEntity(x => x.ChildObject));

3

これを行う最良の方法は、データコンテキストでSaveChanges関数をオーバーライドすることです。

    public override int SaveChanges()
    {
        var added = this.ChangeTracker.Entries().Where(e => e.State == System.Data.EntityState.Added);

        // Do your thing, like changing the state to detached
        return base.SaveChanges();
    }

2

プロファイルを保存しようとすると同じ問題が発生します。すでに挨拶の表を作成し、プロファイルを作成するために新規に作成しています。プロファイルを挿入すると、あいさつ文にも挿入されます。だから私はsavechanges()の前にこうやってみました。

db.Entry(Profile.Salutation).State = EntityState.Unchanged;


1

これは私のために働きました:

// temporarily 'detach' the child entity/collection to have EF not attempting to handle them
var temp = entity.ChildCollection;
entity.ChildCollection = new HashSet<collectionType>();

.... do other stuff

context.SaveChanges();

entity.ChildCollection = temp;

0

ここでは、親をdbsetに追加する前に、子コレクションを親から切断し、既存のコレクションを他の変数にプッシュして後で操作できるようにし、現在の子コレクションを新しい空のコレクションで置き換えます。子コレクションをnullに設定すると、何も失敗しないように見えました。その後、親をdbsetに追加します。このようにして、あなたが望むまで子供は追加されません。


0

私はそれが古い記事であることを知っていますが、コードファーストのアプローチを使用している場合は、マッピングファイルで次のコードを使用することで目的の結果を得ることができます。

Ignore(parentObject => parentObject.ChildObjectOrCollection);

これは基本的にEFに「ChildObjectOrCollection」プロパティをモデルから除外してデータベースにマップされないように指示します。


「無視」はどのコンテキストで使用されていますか?想定されたコンテキストには存在しないようです。
T3.0

0

Entity Framework Core 3.1.0を使用して同様の課題を抱えていましたが、私のリポジトリロジックは非常に汎用的です。

これは私のために働きました:

builder.Entity<ChildEntity>().HasOne(c => c.ParentEntity).WithMany(l =>
       l.ChildEntity).HasForeignKey("ParentEntityId");

「ParentEntityId」は子エンティティの外部キー列名であることに注意してください。このメソッドに上記のコード行を追加しました。

protected override void OnModelCreating(ModelBuilder builder)...
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.