3

EF 5.0、既存のデータベース ワークフローでコード ファーストを使用。データベースには、次のように、SalesOrderLine に必要な外部キーを持つ基本的な SalesOrder および SalesOrderLine テーブルがあります。

public class SalesOrder
{
    public SalesOrder()
    {
        this.SalesOrderLines = new List<SalesOrderLine>();
    }

    public int SalesOrderID { get; set; }
    public int CustomerID { get; set; }
    public virtual Customer Customer { get; set; }

    public virtual ICollection<SalesOrderLine> SalesOrderLines { get; set; }
}

public class SalesOrderLine
{
    public SalesOrderLine()
    {
    }
    public int SalesOrderLineID { get; set; }
    public int SalesOrderID { get; set; }

    public virtual SalesOrder SalesOrder { get; set; }
}
public SalesOrderLineMap()
{
    // Primary Key
    this.HasKey(t => t.SalesOrderLineID);
    // Table & Column Mappings
    this.ToTable("SalesOrderLine");
    this.Property(t => t.SalesOrderLineID).HasColumnName("SalesOrderLineID");
    this.Property(t => t.SalesOrderID).HasColumnName("SalesOrderID");

    // Relationships
    this.HasRequired(t => t.SalesOrder)
        .WithMany(t => t.SalesOrderLines)
        .HasForeignKey(d => d.SalesOrderID);
}

このページによると: http://msdn.microsoft.com/en-us/data/jj713564

...次のように言われています。

次のコードは、外部キーを null に設定して関係を削除します。外部キー プロパティは null 許容でなければならないことに注意してください。

course.DepartmentID = null;

注: 参照が追加された状態 (この例ではコース オブジェクト) の場合、SaveChanges が呼び出されるまで、参照ナビゲーション プロパティは新しいオブジェクトのキー値と同期されません。オブジェクト コンテキストには、追加されたオブジェクトが保存されるまで永続的なキーが含まれていないため、同期は行われません。リレーションシップを設定したらすぐに新しいオブジェクトを完全に同期する必要がある場合は、次のいずれかの方法を使用します。

新しいオブジェクトをナビゲーション プロパティに割り当てる。次のコードは、コースと学科の間の関係を作成します。オブジェクトがコンテキストに関連付けられている場合、コースも department.Courses コレクションに追加され、コース オブジェクトの対応する外部キー プロパティが部門のキー プロパティ値に設定されます。

course.Department = 部門;

...私にはいいですね!

今私の問題:私は次のコードを持っていますが、アサートの両方が失敗します-なぜですか?

    using (MyContext db = new MyContext ())
    {
        SalesOrder so = db.SalesOrders.First();
        SalesOrderLine sol = db.SalesOrderLines.Create();
        sol.SalesOrder = so;

        Trace.Assert(sol.SalesOrderID == so.SalesOrderID);
        Trace.Assert(so.SalesOrderLines.Contains(sol));
    }

両方のオブジェクトがコンテキストに関連付けられています。そうではありませんか? これが機能する前に、SaveChanges() を実行する必要がありますか? もしそうなら、それは少しばかげているように思えますし、新しいオブジェクトが外部キー コレクションに追加されたときに、オブジェクトのすべての参照を手動で設定する必要があるのはかなり面倒です。

- アップデート -

Gert の回答を正しいものとしてマークする必要がありますが、あまり満足していないので、1 日か 2 日待ちます。...その理由は次のとおりです。

次のコードも機能しません。

SalesOrder so = db.SalesOrders.First();
SalesOrderLine sol = db.SalesOrderLines.Create();
db.SalesOrderLines.Add(sol);

sol.SalesOrder = so;
Trace.Assert(so.SalesOrderLines.Contains(sol));

機能する唯一のコード次のとおりです。

SalesOrder so = db.SalesOrders.First();
SalesOrderLine sol = db.SalesOrderLines.Create();

sol.SalesOrder = so;
db.SalesOrderLines.Add(sol);

Trace.Assert(so.SalesOrderLines.Contains(sol));

...つまり、最初にすべての外部キー関係を設定してから、関係と外部キー フィールドが接続される前に TYPE.Add(newObjectOfTYPE) を呼び出す必要があります。つまり、Create を実行してから Add() を実行するまでの間、オブジェクトは基本的に中途半端な状態です。私は(誤って)Create()を使用したので、そしてCreate()は(POCOオブジェクトを返す「new」を使用するのではなく)サブクラス化された動的オブジェクトを返すので、関係のワイヤーアップが処理されると考えていました自分。オブジェクトがサブクラス化された型でなくても、new演算子で作成されたオブジェクトで Add() を呼び出すことができ、それが機能することも奇妙です...

言い換えれば、これはうまくいきます:

    SalesOrder so = db.SalesOrders.First();
    SalesOrderLine sol = new SalesOrderLine();

    sol.SalesOrder = so;
    db.SalesOrderLines.Add(sol);

    Trace.Assert(sol.SalesOrderID == so.SalesOrderID);
    Trace.Assert(so.SalesOrderLines.Contains(sol));

...つまり、それはクールなことですが、それは私を不思議に思います。オブジェクトを適切にアタッチしたい場合、どちらの場合もオブジェクトを常に Add() する必要がある場合、new の代わりに「Create()」を使用するポイントは何ですか?

私にとって最も厄介なのは、次のことが失敗することです。

SalesOrder so = db.SalesOrders.OrderBy(p => p.SalesOrderID).First();
SalesOrderLine sol = db.SalesOrderLines.Create();

sol.SalesOrder = so;
db.SalesOrderLines.Add(sol);

 // NOTE: at this point in time, the SalesOrderId field has indeed been set to the SalesOrderId of the SalesOrder, and the Asserts will pass...
Trace.Assert(sol.SalesOrderID == so.SalesOrderID);
Trace.Assert(so.SalesOrderLines.Contains(sol));

sol.SalesOrder = db.SalesOrders.OrderBy(p => p.SalesOrderID).Skip(5).First();

 // NOTE: at this point in time, the SalesOrderId field is ***STILL*** set to the SalesOrderId of the original SO, so the relationships are not being maintained! 
// The Exception will be thrown!
if (so.SalesOrderID == sol.SalesOrderID)
    throw new Exception("salesorderid not changed");

...それは私にはまったくがらくたのように思えます。バージョン 5 でも EntityFramework は、ライス ペーパー橋の地雷原のように感じます。上記のコードで、SalesOrder プロパティの 2 番目の割り当てで SalesOrderId を同期できないのはなぜですか? ここで欠けている重要なトリックは何ですか?

4

2 に答える 2

3

探していたものが見つかりました!(そして途中でかなりのことを学びました)

EF が動的プロキシで生成していると思ったのは、「変更追跡プロキシ」でした。これらのプロキシ クラスは、ADO.Net エンティティ データ モデルから派生した古い EntityObject 部分クラスのように動作します。

動的に生成されたプロキシ クラスを検討することによって (この投稿で見つけた情報に感謝します: http://davedewinter.com/2010/04/08/viewing-generated-proxy-code-in-the-entity-framework / )、遅延読み込みを行うためにリレーションシップ プロパティの "get" がオーバーライドされていることがわかりましたが、"set" はまったくオーバーライドされていませんでした。変更を検出する「スナップショットと比較」方法。

さらに掘り下げると、最終的にこの 2 つの非常に有益な投稿にたどり着きます 。EFを使用しているすべての人にお勧めします。フレームワーク/

http://blog.oneunicorn.com/2011/12/05/should-you-use-entity-framework-change-tracking-proxies/

残念ながら、EF が変更追跡プロキシを生成するには、次のことが発生する必要があります (上記から引用)。

  • 変更追跡プロキシを有効にするためにクラスが従わなければならない規則は、非常に厳密で制限的です。これにより、エンティティを定義する方法が制限され、プライベート プロパティやプライベート セッターなどの使用が妨げられます。ルールは次のとおりです。クラスはパブリックであり、シールされていない必要があります。すべてのプロパティには、パブリック/保護された仮想ゲッターとセッターが必要です。コレクション ナビゲーション プロパティは、ICollection<T> として宣言する必要があります。IList<T>、List<T>、HashSet<T> などにすることはできません。

  • ルールが非常に制限的であるため、何か問題が発生しやすく、その結果、変更追跡プロキシを取得できません。たとえば、仮想の欠落、セッターの内部化などです。

...彼は続けて、Change-Tracking プロキシに関する他のことと、それらがより良いパフォーマンスまたはより悪いパフォーマンスを示す理由について言及しています。

私の意見では、私は ADO.Net Entity Model の世界から来ており、そのように動作することに慣れているので、変更追跡プロキシ クラスは素晴らしいと思いますが、かなりリッチなクラスもいくつかあります。すべての基準を満たすことができるかどうかはわかりません。さらに、その 2 番目の箇条書きは私をかなり緊張させます (ただし、すべてのエンティティをループし、それぞれに対して Create(0 を実行し、結果のオブジェクトを IEntityWithChangeTracker インターフェイスでテストする) ユニット テストを作成できると思います)。

元の例ですべてのプロパティを仮想に設定することで、実際に IEntityWithChangeTracker 型のプロキシ クラスを取得できましたが、少し感じました...わかりません...「汚い」...それらを使用するため、私はそれを吸う必要があり、割り当てを行うときは常に私の関係の両側を設定することを忘れないでください.

とにかく、助けてくれてありがとう!

乾杯、クリス

于 2013-01-07T19:24:40.860 に答える
1

いいえ、SalesOrderLine solコンテキストに関連付けられていません (ただし、によって作成されますDbSet)。あなたがしなければならない

db.SalesOrderLines.Add(sol);

ChangeTrackerが実行する方法でそれをコンテキストにアタッチしDetectChanges()(DbSet.Add()はこれをトリガーするメソッドの 1 つです) 、新しいオブジェクトを設定して確実に含む関係 fixupも実行します。sol.SalesOrderIDso.SalesOrderLines

いいえ、実行する必要はありませんSaveChanges()が、オブジェクトをコンテキストに追加し、リレーションシップの修正がトリガーされている必要があります。

于 2013-01-06T19:06:56.957 に答える