web-dev-qa-db-ja.com

EF 4:リポジトリパターンでMVCを使用してDbContextのオブジェクトを適切に更新する方法

DBContextのChangeTrackerオブジェクトを使用してAuditLogを実装しようとしていますが、_DbEntityEntry.OriginalValues_が消去されて_DbEntityEntry.CurrentValues_に置き換えられるという問題に遭遇しました。問題はhowであることに気付きました。DbContextで追跡されていたオブジェクトを更新していました(元の投稿: EntityフレームワークDbContext SaveChanges()OriginalValue Incorrect )。

そこで、MVC 3のEntity Framework 4のリポジトリパターンを使用して永続化オブジェクトを更新する適切な方法について、いくつかの助けが必要です。このサンプルコードは、Apressが出したPro Asp.NET MVC 3 FrameworkブックのSportsStoreアプリケーションから採用されました。 。

これが、AdminControllerでの「編集」投稿アクションです。

_[HttpPost]
public ActionResult Edit(Product product)
{
    if (ModelState.IsValid)
    {
        // Here is the line of code of interest...
        repository.SaveProduct(product, User.Identity.Name);

        TempData["message"] = string.Format("{0} has been saved", product.Name);
        return RedirectToAction("Index");
    }
    else
    {
        // there is something wrong with the data values
        return View(product);
    }
}
_

これにより、具象クラスEFProductRepository(IProductRepositoryインターフェイスを実装し、Ninjectを介して注入される)が呼び出されます。具体的なリポジトリクラスのSaveProductメソッドを次に示します。

_public void SaveProduct(Product product, string userID)
{
    if (product.ProductID == 0)
    {
        context.Products.Add(product);
    }
    else
    {
        context.Entry(product).State = EntityState.Modified;
    }
    context.SaveChanges(userID);
}
_

問題(以前のSO投稿)で注目したように)は、context.Entry(product).State = EntityState.Modified;が呼び出されたときに、変更を報告するChangeTrackerの機能を混乱させます。したがって、オーバーロードされたDBContext.SaveChanges(string userID)メソッドでは、ChangeTracker.Entries().Where(p => p.State == System.Data.EntityState.Modified).OriginalValuesオブジェクトに正確な値が表示されません。

EFProductRepository.SaveProductメソッドをこれに更新すると、機能します。

_public void SaveProduct(Product product, string userID)
{
    if (product.ProductID == 0)
    {
        context.Products.Add(product);
    }
    else
    {
        Product prodToUpdate = context.Products
          .Where(p => p.ProductID == product.ProductID).FirstOrDefault();

        if (prodToUpdate != null)
        {
            // Just updating one property to demonstrate....
            prodToUpdate.Name = product.Name;
        }
    }
    context.SaveChanges(userID);
}
_

ChangeTrackerがリポジトリ内のPOCOクラスへの変更を正確に追跡するように、Productオブジェクトを更新してこのシナリオで永続化する適切な方法を知りたいのですが。後者の例を行うことになっていますか(もちろん、更新された可能性のあるすべてのフィールドをコピーすることを除く)、または別のアプローチをとるべきですか?

この例では、「Product」クラスは非常に単純で、文字列プロパティと10進数プロパティしかありません。実際のアプリケーションでは、「複雑な」タイプがあり、POCOクラスは他のオブジェクト(アドレスのリストを持つ人)を参照します。この場合の変更を追跡するために、何か特別なことをする必要があるかもしれません。おそらくこれについての知識は、私がここで受け取るいくつかのアドバイスを変えるでしょう。

19
Joe DePung

どういうわけか、変更を報告するChangeTrackerの機能を台無しにします

いいえ、それは何も混乱させません。変更追跡機能は、変更を行う前に変更追跡がエンティティーを知っているという事実に基づいています。しかし、あなたの場合、変更トラッカーは変更がすでに適用されているエンティティについて通知され、POCOエンティティは元の値に関する情報を保持しません。 POCOエンティティには、現在とオリジナルの両方として解釈される単一の値セットのみがあります。他に何かが必要な場合は、自分でコーディングする必要があります。

私は後者の例を行うことになっていますか

あなたの単純なケースでは、はい、あなたは単に使うことができます:

public void SaveProduct(Product product, string userID)
{
    if (product.ProductID == 0)
    {
        context.Products.Add(product);
    }
    else
    {
        Product prodToUpdate = context.Products
          .Where(p => p.ProductID == product.ProductID).FirstOrDefault();

        if (prodToUpdate != null)
        {
            context.Entry(prodToUpdate).CurrentValues.SetValues(product);
        }
    }

    context.SaveChanges(userID);
}

問題は、これが単純なプロパティと複雑なプロパティに対してのみ機能することです。別の問題は、これによりすべてのプロパティが上書きされるため、たとえばエンティティにUIに表示したくないフィールドがある場合(またはユーザーにフィールドの編集を許可したくない場合)、正しい現在の値をproductに設定する必要があることですそれ以外の場合、現在の値を適用すると、その値は上書きされます。

これを実際のシナリオに適用しようとすると、全体の状況は 大幅に複雑 になります。 EFがこのシナリオをサポートする方法を持たない一般的な解決策がないため、失敗し、多くの場合失敗して、多くのコードを記述してからケースを正確にサポートします。その理由は、EFにはすべてのエンティティと一部の関連付けに対して内部状態マシンがあり、更新、挿入、または削除するすべての単一のエンティティまたは関連付けに対して状態を構成する必要があり、EF内部ルールに従ってそれを行う必要があるためです。エンティティの状態を設定すると、その単一のエンティティの状態は変更されますが、その関係は変更されません。

データベースからすべてのリレーションを含む現在のエンティティを読み込み、エンティティグラフ全体を手動で(コードで)マージするだけです(単に、エンティティグラフを切り離してアタッチし、すべての変更を切り離されたものからアタッチされたものに転送する必要があります)。

35
Ladislav Mrnka