엔티티 프레임 워크 엔티티의 변경 실행 취소


116

이것은 사소한 질문 일 수 있지만 : ADO.NET 엔터티 프레임 워크가 자동으로 변경 사항 (생성 된 엔터티에서)을 추적하고 따라서 원래 값을 유지하므로 엔터티 개체에 대한 변경 사항을 롤백 할 수있는 방법은 무엇입니까?

사용자가 그리드보기에서 "고객"엔터티 집합을 편집 할 수있는 양식이 있습니다.

이제 "수락"과 "되돌리기"버튼이 두 개 있습니다. "수락"을 클릭하면 호출 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
고마워요-정말 도움이되었습니다!
Matt

5
삭제 된 항목에도 원래 값을 설정해야합니다. 먼저 항목을 변경하고 그 후에 삭제했을 수 있습니다.
Bas de Raad

22
EntityState.Unchanged로 설정 State하면 모든 값이 재정의 되므로 메서드 를 호출 할 필요가 없습니다 . Original ValuesSetValues
Abolfazl Hosnoddin 2014

10
이 답변의 더 깨끗한 버전 : stackoverflow.com/a/22098063/2498426
Jerther

1
친구, 이거 대단해! 내가 만든 유일한 수정 사항은 내 리포지토리에서 작동하도록 Items <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 (50ms)입니다. 컬렉션을 반복하는 것은 크기에 따라 더 빠르거나 더 길 수 있습니다. 성능면에서 O (1)은 O (n)에 비해 거의 문제가되지 않습니다. Big O 표기법
Guish 2014-07-02

당신을 따르지 않음-연결 폐기 및 재생성. 기존 프로젝트에서 테스트 한 결과 위의 Rollback절차 보다 다소 빠르게 완료 되었으므로 전체 데이터베이스 상태를 되돌리려는 경우 훨씬 더 나은 선택이됩니다. 롤백은 체리를 선택할 수 있습니다.
majkinetor

'n'은 개체 수를 의미합니다. 연결을 다시 만드는 데 약 50ms가 걸립니다 ... O (1)은 항상 같은 시간임을 의미합니다 50ms+0*n= 50ms. O (n)은 성능이 개체 수에 의해 영향을 받는다는 것을 의미합니다. 성능은 아마도 2ms+0.5ms*n... 96 개 이하의 개체가 더 빠르지 만 시간은 데이터 양에 따라 선형 적으로 증가합니다.
Guish

롤백되지 않은 항목을 체리로 선택하지 않으려면 대역폭에 대해 걱정하지 않는 한 이것이 갈 방법입니다.
Anthony Nichols

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"는 각 엔터티에 대한 데이터베이스에 대한 쿼리를 실행하고 성능 누수를 유발합니다. Microsoft 설명서 ()를 기반으로 :

ClientWins : 개체 컨텍스트의 개체에 대한 속성 변경 사항이 데이터 소스의 값으로 대체되지 않습니다. 다음에 SaveChanges를 호출하면 이러한 변경 사항이 데이터 소스로 전송됩니다.

StoreWins : 개체 컨텍스트의 개체에 대한 속성 변경 사항이 데이터 소스의 값으로 바뀝니다.

.SaveChanges를 실행하면 데이터 소스에 "삭제 된"변경 사항이 커밋되기 때문에 ClientWins도 좋은 아이디어가 아닙니다.

생성 된 새 컨텍스트에서 쿼리를 실행하려고 할 때 컨텍스트를 삭제하고 새 컨텍스트를 만들면 "기본 공급자가 열릴 때 실패했습니다"라는 메시지와 함께 예외가 발생하기 때문에 아직 최선의 방법이 무엇인지 모르겠습니다.

문안 인사,

Henrique Clausing


2

저에게 더 좋은 방법은 EntityState.Unchanged변경 사항을 취소하려는 모든 엔티티 에 설정 하는 것입니다. 이렇게하면 FK에서 변경 사항이 되돌리고 좀 더 명확한 구문이 있습니다.


4
참고 : 엔티티가 다시 변경되면 변경 사항이 다시 적용됩니다.
Nick Whaley 2012 년

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 상태로 설정하면 "내부에서"원래 값이 복원된다고합니다.
Hannish

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.