エンティティフレームワークエンティティの変更を元に戻す


116

これは簡単な質問かもしれませんが、ADO.NETエンティティフレームワークは(生成されたエンティティの)変更を自動的に追跡し、元の値を維持するため、エンティティオブジェクトに加えられた変更をどのようにロールバックできますか?

ユーザーがグリッドビューで「顧客」エンティティのセットを編集できるフォームがあります。

これで、[承諾]と[元に戻す]の2つのボタンができました。[承諾]をクリックすると、呼び出しContext.SaveChanges()が行われ、変更されたオブジェクトがデータベースに書き戻されます。「元に戻す」をクリックすると、すべてのオブジェクトが元のプロパティ値を取得できるようになります。そのためのコードは何でしょうか?

ありがとう

回答:


69

EFで変更操作を元に戻したりキャンセルしたりすることはできません。各エンティティにはObjectStateEntryがありObjectStateManagerます。状態エントリには元の値と実際の値が含まれているため、元の値を使用して現在の値を上書きできますが、エンティティごとに手動で行う必要があります。ナビゲーションのプロパティや関係の変更は反映されません。

「変更を元に戻す」一般的な方法は、コンテキストを破棄してエンティティをリロードすることです。リロードを回避したい場合は、エンティティのクローンを作成し、それらのクローンを新しいオブジェクトコンテキストで変更する必要があります。ユーザーが変更をキャンセルした場合でも、元のエンティティが残ります。


4
@LadislavMrnka確かContext.Refresh()に、元に戻す操作がないというあなたの主張の反例ですか?Refresh()コンテキストを破棄してすべての追跡された変更を失うよりも、使用する方が(つまり、特定のエンティティをより簡単に対象とすることができる)ようです。
Rob

14
@robjb:いいえ。更新では手動で定義した単一のエンティティまたはエンティティのコレクションのみを更新できますが、更新機能は単純なプロパティ(リレーションではなく)にのみ影響します。また、追加または削除されたエンティティの問題も解決しません。
Ladislav Mrnka、2012

153

ダーティアイテムのDbContextのChangeTrackerをクエリします。削除済みアイテムの状態を未変更に、追加済みアイテムを分離に設定します。変更されたアイテムの場合、元の値を使用し、エントリの現在の値を設定します。最後に、変更されたエントリの状態を未変更に設定します。

public void RollBack()
{
    var context = DataContextFactory.GetDataContext();
    var changedEntries = context.ChangeTracker.Entries()
        .Where(x => x.State != EntityState.Unchanged).ToList();

    foreach (var entry in changedEntries)
    {
        switch(entry.State)
        {
            case EntityState.Modified:
                entry.CurrentValues.SetValues(entry.OriginalValues);
                entry.State = EntityState.Unchanged;
                break;
            case EntityState.Added:
                entry.State = EntityState.Detached;
                break;
            case EntityState.Deleted:
                entry.State = EntityState.Unchanged;
                break;
        }
    }
 }

3
ありがとうございます。これは本当に役に立ちました。
マット

5
おそらく、削除されたエントリにも元の値を設定する必要があります。最初にアイテムを変更し、その後それを削除した可能性があります。
Bas de Raad 14年

22
EntityState.Unchangedに設定Stateすると、すべての値もオーバーライドされるため、メソッドを呼び出す必要はありません。Original ValuesSetValues
Abolfazl Hosnoddin 2014

10
この回答のクリーナーバージョン:stackoverflow.com/a/22098063/2498426
Jerther

1
メイト、これはすごい!私が行った唯一の変更は、一般的なバージョンのEntries <T>()を使用して、私のリポジトリで機能するようにすることです。これにより、より詳細に制御でき、エンティティタイプごとにロールバックできます。ありがとう!
Daniel Mackay

33
dbContext.Entry(entity).Reload();

MSDNへの移行:

データベースからエンティティをリロードし、データベースからの値でプロパティ値を上書きします。このメソッドを呼び出した後、エンティティはUnchanged状態になります。

リクエストを介してデータベースに戻すには、いくつかの欠点があることに注意してください。

  • ネットワークトラフィック
  • DB過負荷
  • アプリケーションの応答時間の増加

17

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

dataContext.customer.Context.Refresh(RefreshMode.StoreWins, item);

item元に戻す顧客エンティティはどこにありますか。


12

変更を追跡しない簡単な方法。すべてのエンティティを調べるよりも高速である必要があります。

public void Rollback()
{
    dataContext.Dispose();
    dataContext= new MyEntities(yourConnection);
}

単一のエンティティオブジェクトを作成する時間...これは数ms(50 ms)です。コレクションのループ処理は、サイズに応じてより速くまたは長くなる場合があります。パフォーマンスに関しては、O(1)がO(n)に比べて問題になることはめったにありません。Big O表記
Guish

あなたをフォローしていません-接続の破棄と再作成のパフォーマンス。私はそれを既存のプロジェクトでテストし、上記のRollback手順よりもいくらか速く終了しました。これにより、データベース全体の状態を元に戻したい場合は、はるかに良い選択になります。ロールバックはチェリーを選ぶことができます。
majkinetor 2014

「n」はオブジェクトの数を意味します。接続の再作成には約50ミリ秒かかります... O(1)は常に同じ時間であることを意味し50ms+0*n= 50msます。O(n)は、パフォーマンスがオブジェクトの数に影響されることを意味します...パフォーマンスはおそらく2ms+0.5ms*n...なので、96個のオブジェクトは高速ですが、データ量に比例して時間が増加します。
2014

チェリーを使用しない場合は、ロールバックされる(されない)ものを選択します。これは、帯域幅が心配されていない場合に適した方法です。
アンソニーニコルズ

6
// Undo the changes of all entries. 
foreach (DbEntityEntry entry in context.ChangeTracker.Entries()) 
{ 
    switch (entry.State) 
    { 
        // Under the covers, changing the state of an entity from  
        // Modified to Unchanged first sets the values of all  
        // properties to the original values that were read from  
        // the database when it was queried, and then marks the  
        // entity as Unchanged. This will also reject changes to  
        // FK relationships since the original value of the FK  
        // will be restored. 
        case EntityState.Modified: 
            entry.State = EntityState.Unchanged; 
            break; 
        case EntityState.Added: 
            entry.State = EntityState.Detached; 
            break; 
        // If the EntityState is the Deleted, reload the date from the database.   
        case EntityState.Deleted: 
            entry.Reload(); 
            break; 
        default: break; 
    } 
} 

それは私のために働いた。ただし、古いデータを取得するには、コンテキストからデータをリロードする必要があります。ソースはこちら


3

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

dataContext.customer.Context.Refresh(RefreshMode.StoreWins, item);

item元に戻す顧客エンティティはどこにありますか?」


SQL AzureでObjectContext.Refreshを使用してテストを行ったところ、 "RefreshMode.StoreWins"が各エンティティのデータベースに対してクエリを実行し、パフォーマンスリークを引き起こしています。マイクロソフトのドキュメントに基づいて():

ClientWins:オブジェクトコンテキストでオブジェクトに加えられたプロパティの変更は、データソースの値で置き換えられません。SaveChangesへの次の呼び出しで、これらの変更はデータソースに送信されます。

StoreWins:オブジェクトコンテキストでオブジェクトに加えられたプロパティの変更は、データソースの値に置き換えられます。

.SaveChangesを実行すると「破棄された」変更がデータソースにコミットされるため、ClientWinsも良いアイデアではありません。

コンテキストを破棄して新しいコンテキストを作成すると例外が発生し、作成された新しいコンテキストでクエリを実行しようとすると、「基になるプロバイダーが開くときに失敗しました」というメッセージが表示されるので、私はまだ最善の方法を知りません。

よろしく、

ヘンリック句


2

私としては、EntityState.Unchanged変更を元に戻したいすべてのエンティティに設定するのがより良い方法です。これにより、FKで変更が元に戻され、構文がもう少し明確になります。


4
注:エンティティが再度変更されると、変更は元に戻ります。
Nick Whaley

2

私はこれが私のコンテキストでうまく機能していることがわかりました:

Context.ObjectStateManager.ChangeObjectState(customer, EntityState.Unchanged);


1
これにより、エンティティへの変更がを呼び出しDbContext.SaveChanges()ても保持されなくなると思いますが、エンティティの値を元の値に戻しません。エンティティの状態が後で変更されて変更された場合、おそらく以前のすべての変更が保存時に保持されますか?
Carl G

1
このリンクを確認してくださいcode.msdn.microsoft.com/How-to-undo-the-changes-in-00aed3c4これは、エンティティをUnchaged状態に設定すると、「カバーの下」で元の値が復元されることを示しています。
2013年

2

これはMrnkaが話していることの例です。次のメソッドは、エンティティの現在の値を元の値で上書きし、データベースを呼び出しません。これを行うには、DbEntityEntryのOriginalValuesプロパティを使用し、リフレクションを使用して一般的な方法で値を設定します。(これはEntityFramework 5.0以降で機能します)

/// <summary>
/// Undoes any pending updates 
/// </summary>
public void UndoUpdates( DbContext dbContext )
{
    //Get list of entities that are marked as modified
    List<DbEntityEntry> modifiedEntityList = 
        dbContext.ChangeTracker.Entries().Where(x => x.State == EntityState.Modified).ToList();

    foreach(  DbEntityEntry entity in modifiedEntityList ) 
    {
        DbPropertyValues propertyValues = entity.OriginalValues;
        foreach (String propertyName in propertyValues.PropertyNames)
        {                    
            //Replace current values with original values
            PropertyInfo property = entity.Entity.GetType().GetProperty(propertyName);
            property.SetValue(entity.Entity, propertyValues[propertyName]); 
        }
    }
}

1

レガシーオブジェクトコンテキストでEF 4を使用しています。上記の解決策はどれも私のために直接これに答えることはありませんでした-それは正しい方向に私を押すことによって長期的には答えましたが。

メモリ内でぶら下がっている一部のオブジェクト(遅延読み込みがいまいましい!!)はまだコンテキストにアタッチされていますが、まだ読み込まれていない子があるため、コンテキストを破棄して再構築することはできません。これらの場合、データベースを改造したり、既存の接続を削除したりせずに、すべてを元の値に戻す必要があります。

以下は、この同じ問題の解決策です。

    public static void UndoAllChanges(OurEntities ctx)
    {
        foreach (ObjectStateEntry entry in
            ctx.ObjectStateManager.GetObjectStateEntries(~EntityState.Detached))
        {
            if (entry.State != EntityState.Unchanged)
            {
                ctx.Refresh(RefreshMode.StoreWins, entry.Entity);
            }
        }
    }

これが他の人の役に立つことを願っています。


0

上記のいくつかの優れたアイデアから、私はICloneableを実装し、次に単純な拡張メソッドを実装することを選択しました。

ここにあります:C#でジェネリックリストを複製するにはどうすればよいですか?

として使用する:

ReceiptHandler.ApplyDiscountToAllItemsOnReciept(LocalProductsOnReciept.Clone(), selectedDisc);

このようにして、製品エンティティリストを複製し、各アイテムに割引を適用でき、元のエンティティの変更を元に戻すことを心配する必要がなくなりました。DBContextとやり取りして更新を要求したり、ChangeTrackerを操作したりする必要はありません。私はEF6を十分に活用していないと言うかもしれませんが、これは非常に素晴らしくシンプルな実装であり、DBのヒットを回避します。これがパフォーマンスに影響したかどうかはわかりません。

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