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


107

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;

ObjectPostのインスタンスから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の勝利を憎みます:(。これは、EFがMVC環境でこれを行うことができないことを意味しますEF4オブジェクトタイプが使用されます:(。スナップショットベースの変更追跡では、変更されたBlogPostが既存のPKのタグとの関係を持っていることがわかるはずです。

ところで、私は1対多の関係でも同じ問題が発生すると思います(Googleと私の同僚はそう言っています)。私はそれを自宅で試してみるつもりですが、それがうまくいくとしても、私のアプリでの6つの多対多の関係で私を助けません:(。


コードを投稿してください。これは一般的なシナリオです。
John Farrell

1
私はこの問題の自動解決策を持っています。以下の回答には隠されているので、多くの人はそれを逃しますが、地獄を救うため、ぜひご覧ください。ここの投稿
brentmckendrick

@brentmckendrick別のアプローチの方が良いと思います。変更されたオブジェクトグラフ全体をネットワーク経由で送信する代わりに、デルタを送信するだけではどうですか。その場合は、生成されたDTOクラスも必要ありません。これについてどちらかの方法で意見がある場合は、stackoverflow.com/questions/1344066/calculate-object-deltaで議論してみましょう。
HappyNomad 2013年

回答:


145

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

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

編集:

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

問題の背景

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

問題の説明

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

解決

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

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

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

手動同期は、MSDNドキュメントによってソリューションとして提案されています。オブジェクトのアタッチとデタッチは、次のように述べています。

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

言及されているメソッドは、ObjectStateManagerのChangeObjectStateおよびChangeRelationshipState =手動変更追跡です。同様の提案が他のMSDNドキュメントの記事にあります:関係の定義と管理は言う:

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

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

解決の理由

EFには、RefreshLoadApplyCurrentValuesApplyOriginalValuesMergeOptionなどの多くの「役立つ」操作と設定があります。しかし、私の調査では、これらの機能はすべて単一のエンティティに対してのみ機能し、スカラーのプロパティのみに影響します(=ナビゲーションプロパティや関係には影響しません)。エンティティにネストされた複合型を使用してこのメ​​ソッドをテストするのではなく、

その他の提案されたソリューション

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

結論

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

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

編集2:

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


7
これは私がSOで読んだ最良の答えの1つです。トピックに関する多くのMSDNの記事、ドキュメント、およびブログ投稿がうまく行き渡らなかったことを明確に述べました。EF4は、「切り離された」エンティティからの関係の更新を本質的にサポートしていません。自分で実装するためのツールのみを提供します。ありがとうございました!
tyriker 2010年

1
では、数か月後、EF4と比較してこの問題に関連するNHibernateはどうですか?
CallMeLaNN 2011

1
これはNHibernateで非常によくサポートされています:-)手動でマージする必要はありません。私の例では、3レベルの深いオブジェクトグラフです。質問には回答があり、各回答にはコメントがあり、質問にもコメントがあります。NHibernateのは、あなたのオブジェクトグラフ、それがどのように複雑に関係なくマージ/持続可能ienablemuch.com/2011/01/nhibernate-saves-your-whole-object.html:別満足NHibernateのユーザーcodinginstinct.com/2009/11/...
マイケルはブエン

2
私が今まで読んだ中で最高の説明の1つ!!
どうも

2
EFチームはこのポストEF6に対処する予定です。あなたはのために投票したいことがありentityframework.codeplex.com/workitem/864
エリック・J.

19

私は上記のLadislavによって説明された問題の解決策を持っています。提供されたグラフと永続化されたグラフのdiffに基づいて自動的に追加、更新、削除を実行するDbContextの拡張メソッドを作成しました。

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

http://refactorthis.wordpress.com/2012/12/11/introducing-graphdiff-for-entity-framework-code-first-allowing-automated-updates-of-a-が役立つかどうかを確認してください。分離エンティティのグラフ/

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


私はあなたがこの質問を簡単に解決できると確信しています。私はそれで悪い時間を過ごしています。
Shimmy Weitzhandler 2013

1
こんにちはシミー、申し訳ありませんが、最終的に見てみる時間がありました。今夜調べます。
brentmckendrick 2013

このライブラリは素晴らしいですし、私にとても時間を節約してくれました!どうも!
lordjeb

9

私はそれが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();

複数の関係でも機能します


7

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

Entity Frameworkチームから:

これは私たちが認識しているユーザビリティの問題であり、EF6以降でさらに作業を行うことを検討しており、計画しているものです。問題を追跡するためにこの作業項目を作成しました:http : //entityframework.codeplex.com/workitem/864この作業項目には、このためのユーザー音声項目へのリンクも含まれています-持っている場合は投票することをお勧めしますまだそうしていない。

これがあなたに影響を与える場合は、機能に投票してください

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


EF6以降?それでは、楽観的なケースになるのはどの年でしょうか。
ケツァルコアトル2013

@quetzalcoatl:少なくともそれは彼らのレーダーにあります:-) EFはEF 1以来おおざっぱな道を歩んできましたが、まだ道は進んでいます。
エリックJ.

1

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

親エンティティで関係を使用せずに、子エンティティを追加および削除しただけの場合、すべてが正常に機能することがわかりました。

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}))
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.