web-dev-qa-db-ja.com

EF4 POCOオブジェクトの変更を保存するときに関係を更新する

Entity Framework 4、POCOオブジェクト、ASP.Net MVC2。私は多対多の関係を持っています。たとえば、BlogPostとTagエンティティの間には関係があります。これは、T4で生成されたPOCO BlogPostクラスに次のものがあることを意味します。

public virtual ICollection<Tag> Tags {
    // getter and setter with the magic FixupCollection
}
private ICollection<Tag> _tags;

ObjectContextのインスタンスからBlogPostと関連するタグを要求し、別のレイヤー(MVCアプリケーションで表示)に送信します。後で、プロパティと関係が変更された更新済みのBlogPostを受け取ります。たとえば、「A」、「B」、「C」というタグがあり、新しいタグは「C」と「D」です。私の特定の例では、新しいタグはなく、タグのプロパティは変更されないため、保存する必要があるのは変更された関係のみです。次に、これを別のObjectContextに保存する必要があります。 (更新:同じコンテキストインスタンスで実行しようとしましたが、失敗しました。)

問題:関係を適切に保存できない。私が見つけたすべてを試しました:

  • Controller.UpdateModelおよびController.TryUpdateModelは機能しません。
  • コンテキストから古いBlogPostを取得してからコレクションを変更しても機能しません。 (次の点から異なる方法で)
  • これ はおそらく動作しますが、これが単なる解決策ではなく、解決策であることを願っています:(。
  • 可能なあらゆる組み合わせでBlogPostおよび/またはタグのAttach/Add/ChangeObjectState関数を試しました。失敗しました。
  • これ は必要なように見えますが、機能しません(修正しようとしましたが、問題は解決できません)。
  • コンテキストの関係オブジェクトをChangeState/Add/Attach/...で試しました。失敗しました。

「機能しない」とは、ほとんどの場合、エラーが発生せず、少なくともBlogPostのプロパティが保存されるまで、指定された「ソリューション」に取り組んだことを意味します。リレーションシップで何が起こるかは異なります。通常、タグは新しいPKでタグテーブルに再び追加され、保存されたBlogPostは元のPKではなく、それらを参照します。もちろん、返されたタグにはPKがあり、保存/更新メソッドの前にPKをチェックしますが、それらはデータベース内のものと等しいため、おそらくEFは新しいオブジェクトであり、それらのPKは一時的なものであると考えます。

私が知っている問題で、自動化された簡単な解決策を見つけることができない可能性があります:POCOオブジェクトのコレクションが変更されると、上記の仮想コレクションプロパティによって発生するはずです。FixupCollectionトリックは反対側の逆参照を更新するためです多対多の関係。しかし、ビューが更新されたBlogPostオブジェクトを「返す」場合、それは起こりませんでした。これは、私の問題に対する単純な解決策がないかもしれないことを意味しますが、それは私を非常に悲しくさせ、EF4-POCO-MVCの勝利を嫌います:(。 EF4オブジェクトタイプが使用されます:(。スナップショットベースの変更追跡は、変更されたBlogPostが既存のPKを持つタグとの関係を持っていることを発見するはずです。

Btw:同じ問題は一対多の関係でも発生すると思います(グーグルと私の同僚はそう言っています)。私はそれを家で試してみますが、それがうまくいくとしても、私のアプリの6つの多対多の関係で私を助けません:(。

107
peterfoldi

この方法で試してみましょう:

  • BlogPostをコンテキストに添付します。オブジェクトをコンテキストに関連付けた後、オブジェクトの状態、すべての関連オブジェクト、およびすべてのリレーションが変更なしに設定されます。
  • Context.ObjectStateManager.ChangeObjectStateを使用して、BlogPostをModifiedに設定します
  • タグコレクションを反復処理する
  • Context.ObjectStateManager.ChangeRelationshipStateを使用して、現在のTagとBlogPost間の関係の状態を設定します。
  • 変更内容を保存

編集:

私のコメントの1つは、EFがあなたのためにマージを行うという誤った希望を与えたと思います。私はこの問題で多くのことをしました、そして私の結論は、EFはあなたのためにこれをしないと言います。 [〜#〜] msdn [〜#〜] で私の質問も見つけたと思います。実際には、インターネット上にはそのような質問がたくさんあります。問題は、このシナリオに対処する方法が明確に述べられていないことです。それでは、問題を見てみましょう。

問題の背景

EFは、どのレコードを更新、挿入、または削除する必要があるかを永続性が把握できるように、エンティティの変更を追跡する必要があります。問題は、変更を追跡するのがObjectContextの責任であることです。 ObjectContextは、添付されたエンティティの変更のみを追跡できます。 ObjectContextの外部で作成されたエンティティはまったく追跡されません。

問題の説明

上記の説明に基づいて、エンティティが常にコンテキストに接続されている接続シナリオ-ウィンフォームアプリケーションに典型的なEFの方が適していることを明確に述べることができます。 Webアプリケーションには、要求処理後にコンテキストが閉じられ、エンティティコンテンツがHTTP応答としてクライアントに渡される、切断シナリオが必要です。次のHTTP要求は、エンティティの変更されたコンテンツを提供します。このコンテンツは、再作成し、新しいコンテキストにアタッチして永続化する必要があります。通常、レクリエーションはコンテキストスコープ(永続性を無視する階層化アーキテクチャ)の外側で行われます。

ソリューション

では、そのような切断されたシナリオにどう対処するのでしょうか? POCOクラスを使用する場合、変更追跡を処理する方法は3つあります。

  • スナップショット-同じコンテキストが必要=接続されていないシナリオでは役に立たない
  • 動的追跡プロキシ-同じコンテキストが必要です=接続されていないシナリオでは役に立たない
  • 手動同期。

単一エンティティの手動同期は簡単なタスクです。エンティティをアタッチし、挿入のためにAddObjectを呼び出し、削除のためにDeleteObjectを呼び出すか、更新のためにObjectStateManagerの状態をModifiedに設定するだけです。単一のエンティティではなくオブジェクトグラフを処理する必要がある場合、本当の痛みが生じます。この痛みは、独立したアソシエーション(外部​​キープロパティを使用しないもの)と多対多のリレーションを処理する必要がある場合はさらに悪化します。その場合、オブジェクトグラフの各エンティティだけでなく、オブジェクトグラフの各リレーションも手動で同期する必要があります。

手動同期は、MSDNのドキュメントで解決策として提案されています。 オブジェクトのアタッチとデタッチ 言います:

オブジェクトは、変更されていない状態でオブジェクトコンテキストにアタッチされます。オブジェクトが切り離された状態で変更されたことがわかっているために、オブジェクトまたは関係の状態を変更する必要がある場合は、次のいずれかの方法を使用します。

言及されているメソッドは、ObjectStateManagerのChangeObjectStateおよびChangeRelationshipState =手動変更追跡です。同様の提案は、他のMSDNドキュメント記事にあります: Defining and Managing Relationships says:

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

さらに、EFのこの動作を厳密に批判するEF v1に関連する ブログ投稿 があります。

解決策の理由

EFには、 RefreshLoadApplyCurrentValuesApplyOriginalValuesMergeOption など。ただし、私の調査では、これらの機能はすべて単一のエンティティに対してのみ機能し、スカラーの優先度にのみ影響します(=ナビゲーションプロパティおよびリレーションには影響しません)。むしろ、エンティティにネストされた複雑なタイプでこのメソッドをテストすることはありません。

他の提案された解決策

実際のマージ機能の代わりに、EFチームは Self Tracking Entities (STE)と呼ばれるものを提供しますが、これは問題を解決しません。まず、STEは、処理全体に同じインスタンスが使用されている場合にのみ機能します。 Webアプリケーションでは、インスタンスをビューステートまたはセッションに保存しない限り、そうではありません。そのため、EFを使用することに非常に不満を抱いており、NHibernateの機能を確認します。最初の観察では、NHibernateにはおそらく 機能 があると言われています。

結論

この仮定は、MSDNフォーラムの別のリンク 関連する質問 への単一のリンクで終わります。 Zeeshan Hiraniの答えを確認してください。彼は Entity Framework 4.0 Recipes の著者です。オブジェクトグラフの自動マージがサポートされていないと彼が言う場合、彼を信じています。

しかし、まだ完全に間違っている可能性があり、EFにはいくつかの自動マージ機能が存在します。

編集2:

ご覧のとおり、これは2007年の提案として MS Connect に既に追加されています。MSは、次のバージョンで行うべきこととしてこれを閉じましたが、実際にはSTE以外はこのギャップを改善するために何もしていません。

143
Ladislav Mrnka

ラディスラフが上記で説明した問題の解決策があります。提供されたグラフと永続化されたグラフの差分に基づいて追加/更新/削除を自動的に実行するDbContextの拡張メソッドを作成しました。

現在、Entity Frameworkを使用して、連絡先の更新を手動で実行し、各連絡先が新しいかどうかを確認して追加し、更新と編集を確認し、削除したかどうかを確認してからデータベースから削除する必要があります。大規模なシステムのいくつかの異なる集約に対してこれを行う必要がある場合、より良い、より一般的な方法が必要であることに気付き始めます。

見て、それが役立つかどうかを確認してください http://refactorthis.wordpress.com/2012/12/11/introducing-graphdiff-for-entity-framework-code-first-allowing-automated-updates-分離された図形のグラフ/

ここでコードに直接進むことができます https://github.com/refactorthis/GraphDiff

19
brentmckendrick

OPに遅れていることは知っていますが、これは非常に一般的な問題であるため、他の人に役立つ場合に投稿しました。私はこの問題をいじくり回してきましたが、私はかなり簡単な解決策を得たと思います。

  1. 状態をModifiedに設定して、メインオブジェクト(たとえば、ブログ)を保存します。
  2. 更新する必要があるコレクションを含む更新されたオブジェクトのデータベースを照会します。
  3. コレクションに含めるエンティティを.ToList()でクエリおよび変換します。
  4. メインオブジェクトのコレクションをステップ3で取得したリストに更新します。
  5. 変更内容を保存();

次の例では、「dataobj」と「_categories」はコントローラーが受信するパラメーターです。「dataobj」はメインオブジェクトです。「_ categories」は、ユーザーがビューで選択したカテゴリーのIDを含むIEnumerableです。

    db.Entry(dataobj).State = EntityState.Modified;
    db.SaveChanges();
    dataobj = db.ServiceTypes.Include(x => x.Categories).Single(x => x.Id == dataobj.Id);
    var it = _categories != null ? db.Categories.Where(x => _categories.Contains(x.Id)).ToList() : null;
    dataobj.Categories = it;
    db.SaveChanges();

複数のリレーションでも機能します

9
c0y0teX

Entity Frameworkチームは、これがユーザビリティの問題であることを認識しており、EF6以降に対処する予定です。

Entity Frameworkチームから:

これは、私たちが認識しているユーザビリティの問題であり、私たちが考えていたものであり、EF6以降でさらに作業を行う予定です。問題を追跡するためにこのワークアイテムを作成しました: http://entityframework.codeplex.com/workitem/864 ワークアイテムには、このためのユーザー音声アイテムへのリンクも含まれています-まだ投票していない場合は投票します。

これが影響する場合は、次のURLで機能に投票してください

http://entityframework.codeplex.com/workitem/864

7
Eric J.

すべての答えは問題を説明するのに素晴らしかったが、実際に問題を解決した人はいなかった。

親エンティティでリレーションシップを使用せず、子エンティティを追加および削除するだけで、すべてが正常に機能することがわかりました。

VBで申し訳ありませんが、それは私が取り組んでいるプロジェクトが書かれているものです。

親エンティティ「Report」は、「ReportRole」と1対多の関係を持ち、プロパティ「ReportRoles」を持っています。新しいロールは、Ajax呼び出しからコンマ区切りの文字列で渡されます。

最初の行はすべての子エンティティを削除し、「db.ReportRoles.Remove(f)」の代わりに「report.ReportRoles.Remove(f)」を使用すると、エラーが発生します。

report.ReportRoles.ToList.ForEach(Function(f) db.ReportRoles.Remove(f))
Dim newRoles = If(String.IsNullOrEmpty(model.RolesString), New String() {}, model.RolesString.Split(","))
newRoles.ToList.ForEach(Function(f) db.ReportRoles.Add(New ReportRole With {.ReportId = report.Id, .AspNetRoleId = f}))
1
Alan Bridges