Programing

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

crosscheck 2020. 8. 11. 07:52
반응형

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


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

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

이제 "수락"과 "되돌리기"버튼이 두 개 있습니다. "수락"을 클릭하면 호출 Context.SaveChanges()하면 변경된 개체가 데이터베이스에 다시 기록됩니다. "되돌리기"를 클릭하면 모든 개체가 원래 속성 값을 가져 오도록하겠습니다. 그 코드는 무엇입니까?

감사


EF에는 되돌리기 또는 변경 취소 작업이 없습니다. 각 엔티티가 ObjectStateEntry에서 ObjectStateManager. 상태 항목에는 원래 값과 실제 값이 포함되어 있으므로 원래 값을 사용하여 현재 값을 덮어 쓸 수 있지만 각 엔터티에 대해 수동으로 수행해야합니다. 탐색 속성 / 관계의 변경 사항을 무시하지 않습니다.

"변경 사항을 되 돌리는"일반적인 방법은 컨텍스트를 삭제하고 엔티티를 다시로드하는 것입니다. 다시로드하지 않으려면 엔티티의 복제본을 만들고 새 개체 컨텍스트에서 해당 복제본을 수정해야합니다. 사용자가 변경 사항을 취소해도 원래 엔티티가 유지됩니다.


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;
        }
    }
 }

dbContext.Entry(entity).Reload();

MSDN에 동의 :

데이터베이스의 값으로 속성 값을 덮어 쓰는 데이터베이스에서 엔터티를 다시로드합니다. 이 메서드를 호출 한 후 엔티티는 Unchanged 상태가됩니다.

요청을 통해 데이터베이스로 되 돌리면 몇 가지 단점이 있습니다.

  • 네트워크 트래픽
  • DB 과부하
  • 증가 된 애플리케이션 응답 시간

이것은 나를 위해 일했습니다.

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

item되돌릴 고객 엔티티는 어디에 있습니까 ?


변경 사항을 추적하지 않는 쉬운 방법. 모든 엔티티를 보는 것보다 더 빠릅니다.

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

// 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; 
    } 
} 

그것은 나를 위해 일했습니다. 그러나 이전 데이터를 가져 오려면 컨텍스트에서 데이터를 다시로드해야합니다. 여기에 소스


"이것은 나를 위해 일했습니다.

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

item되돌릴 고객 엔티티는 어디에 있습니까 ? "


I have made tests with ObjectContext.Refresh in SQL Azure, and the "RefreshMode.StoreWins" fires a query against database for each entity and causes a performance leak. Based on microsoft documentation ():

ClientWins : Property changes made to objects in the object context are not replaced with values from the data source. On the next call to SaveChanges, these changes are sent to the data source.

StoreWins : Property changes made to objects in the object context are replaced with values from the data source.

ClientWins isn't a good ideia neither, because firing .SaveChanges will commit "discarded" changes to the datasource.

I dont' know what's the best way yet, because disposing the context and creating a new one is caused a exception with message: "The underlying provider failed on open" when I try to run any query on a new context created.

regards,

Henrique Clausing


As for me, better method to do it is to set EntityState.Unchanged on every entity you want to undo changes on. This assures changes are reverted on FK and has a bit more clear syntax.


I found this to be working fine in my context:

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


This is an example of what Mrnka is talking about. The following method overwrites an entity's current values with the original values and doesn't call out the database. We do this by making use of the OriginalValues property of DbEntityEntry, and make use of reflection to set values in a generic way. (This works as of 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]); 
        }
    }
}

We are using EF 4, with the Legacy Object context. None of the above solutions directly answered this for me -- although it DID answer it in the long run by pushing me in the right direction.

We can't just dispose and rebuild the context because some of the objects we have hanging around in memory (damn that lazy loading!!) are still attached to the context but have children that are yet-to-be-loaded. For these cases we need to bump everything back to original values without hammering the database and without dropping the existing connection.

Below is our solution to this same issue:

    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);
            }
        }
    }

I hope this helps others.


Some good ideas above, I chose to implement ICloneable and then a simple extension method.

Found here: How do I clone a generic list in C#?

To be used as:

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

This way I was able to clone my product entities list, apply a discount to each item and not have to worry about reverting any changes on the original entity. No need to talk with the DBContext and ask for a refresh or work with the ChangeTracker. You might say I am not making full use of EF6 but this is a very nice and simple implementation and avoids a DB hit. I cannot say whether or not this has a performance hit.

참고URL : https://stackoverflow.com/questions/5466677/undo-changes-in-entity-framework-entities

반응형