Varlık çerçevesi varlıklardaki değişiklikleri geri alın


116

bu önemsiz bir soru olabilir, ancak: ADO.NET varlık çerçevesi değişiklikleri (oluşturulan varlıklarda) otomatik olarak izlediğinden ve bu nedenle orijinal değerleri koruduğundan, varlık nesnelerinde yapılan değişiklikleri nasıl geri alabilirim?

Kullanıcının ızgara görünümünde bir dizi "Müşteri" varlığını düzenlemesine izin veren bir formum var.

Şimdi iki "Kabul Et" ve "Geri Al" düğmem var: "Kabul Et" tıklanırsa, çağırırım Context.SaveChanges()ve değiştirilen nesneler veritabanına geri yazılır. "Geri Döndür" tıklanırsa, tüm nesnelerin orijinal özellik değerlerini almasını istiyorum. Bunun kodu ne olabilir?

Teşekkürler

Yanıtlar:


69

EF'de geri döndürme veya iptal etme işlemi yoktur. Her varlığın ObjectStateEntrygirişi vardır ObjectStateManager. Durum girişi orijinal ve gerçek değerleri içerir, böylece mevcut değerlerin üzerine yazmak için orijinal değerleri kullanabilirsiniz, ancak bunu her varlık için manuel olarak yapmanız gerekir. Gezinme özelliklerindeki / ilişkilerindeki değişiklikleri geri almayacaktır.

"Değişiklikleri geri almanın" yaygın yolu, bağlamı atmak ve varlıkları yeniden yüklemektir. Yeniden yüklemeden kaçınmak istiyorsanız, varlık klonlarını oluşturmalı ve bu klonları yeni nesne bağlamında değiştirmelisiniz. Kullanıcı değişiklikleri iptal ederse, orijinal varlıklara sahip olursunuz.


4
@LadislavMrnka Geri Context.Refresh()dönüş operasyonu olmadığı yönündeki iddianıza kesinlikle bir karşı örnek mi? Kullanımı Refresh(), bağlamı elden çıkarmaktan ve izlenen tüm değişiklikleri kaybetmekten daha iyi bir yaklaşım (yani belirli varlıkları hedef almak daha kolay) gibi görünmektedir.
Rob

14
@robjb: Hayır. Yenileme, yalnızca elle tanımladığınız tek bir varlığı veya varlık koleksiyonunu yenileyebilir, ancak işlevselliği yenilemek yalnızca basit özellikleri etkiler (ilişkileri değil). Ayrıca, eklenen veya silinen varlıklarla ilgili sorunu da çözmez.
Ladislav Mrnka

153

Kirli öğeler için DbContext'in Query ChangeTracker'ı. Silinen öğelerin durumunu değiştirilmedi ve eklenen öğeler çıkarıldı olarak ayarla. Değiştirilen öğeler için orijinal değerleri kullanın ve girişin mevcut değerlerini ayarlayın. Son olarak değiştirilmiş giriş durumunu değiştirilmedi olarak ayarlayın:

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
Teşekkürler - bu bana gerçekten yardımcı oldu!
Matt

5
Muhtemelen orijinal değerleri de silinmiş girişlere ayarlamalısınız. İlk önce bir öğeyi değiştirmiş ve daha sonra silmiş olabilirsiniz.
Bas de Raad

22
EntityState.Unchanged olarak ayarlamak State, ile tüm değerleri de geçersiz kılar , böylece yöntemi çağırmaya gerek yoktur . Original ValuesSetValues
Abolfazl Hosnoddin

10
Bu cevabın daha
anlaşılır

1
Dostum, bu harika! Yaptığım tek değişiklik, Girişler <T> () 'nin jenerik sürümünü kullanmak, böylece depolarım için çalışması oldu. Bu bana daha fazla kontrol sağlıyor ve varlık türüne göre geri dönebilirim. Teşekkürler!
Daniel Mackay

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

Accroding MSDN :

Varlığı veritabanından yeniden yükler ve veritabanındaki değerlerle özellik değerlerinin üzerine yazar. Varlık, bu yöntemi çağırdıktan sonra Değiştirilmemiş durumda olacaktır.

Veritabanına istek üzerinden geri dönmenin bazı dezavantajları olduğunu unutmayın:

  • ağ trafiği
  • DB aşırı yükü
  • artan uygulama yanıt süresi

17

Bu benim için çalıştı:

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

itemGeri alınacak müşteri varlığı nerede .


12

Herhangi bir değişikliği izlemeden kolay yol. Her varlığa bakmaktan daha hızlı olmalı.

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

Birkaç ms (50 ms) olan tek bir yetki nesnesi oluşturma zamanı. Koleksiyonda döngü yapmak, boyutuna bağlı olarak daha hızlı veya daha uzun olabilir. Performans açısından O (1), O (n) ile karşılaştırıldığında nadiren bir sorundur. Büyük O notasyonu
Guish

Sizi takip etmiyor - bağlantıyı atma ve yeniden oluşturma performansı. Mevcut projede test ettim ve yukarıdaki Rollbackprosedürden biraz daha hızlı bitti , bu da tüm veritabanı durumunu geri almak istiyorsa çok daha iyi bir seçim haline getiriyor. Geri alma, kiraz seçebilir.
majkinetor

'n', nesnelerin sayısı anlamına gelir. Bağlantının yeniden oluşturulması yaklaşık 50 ms sürer ... O (1) her zaman aynı zamanda olduğu anlamına gelir 50ms+0*n= 50ms. O (n), performansın nesne sayısından etkilendiği anlamına gelir ... performans belki 2ms+0.5ms*n... yani 96 nesnenin altında daha hızlı olur, ancak zaman veri miktarı ile doğrusal olarak artar.
Guish

Neyin geri alındığını (n't) kiraz seçmeyecekseniz, bant genişliği konusunda endişelenmediğiniz sürece, bu gitmenin yoludur.
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; 
    } 
} 

Benim için çalıştı. Ancak, eski verileri getirmek için verilerinizi bağlamdan yeniden yüklemeniz gerekir. Buraya kaynak


3

"Bu benim için çalıştı:

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

itemGeri alınacak müşteri varlığı nerede . "


SQL Azure'da ObjectContext.Refresh ile testler yaptım ve "RefreshMode.StoreWins" her varlık için veritabanına karşı bir sorgu başlatıyor ve performans sızıntısına neden oluyor. Microsoft belgelerine () göre:

ClientWins: Nesne bağlamındaki nesnelere yapılan özellik değişiklikleri, veri kaynağındaki değerlerle değiştirilmez. Bir sonraki SaveChanges çağrısında, bu değişiklikler veri kaynağına gönderilir.

StoreWins: Nesne bağlamındaki nesnelere yapılan özellik değişiklikleri, veri kaynağındaki değerlerle değiştirilir.

ClientWins de iyi bir fikir değildir, çünkü .SaveChanges, veri kaynağına "atılmış" değişiklikleri uygulayacaktır.

Henüz en iyi yolun ne olduğunu bilmiyorum, çünkü bağlamı elden çıkarmak ve yeni bir tane oluşturmak, oluşturulan yeni bir bağlamda herhangi bir sorgu çalıştırmaya çalıştığımda "Temel sağlayıcı açmada başarısız oldu" mesajıyla bir istisnaya neden oluyor.

Saygılarımızla,

Henrique Clausing


2

Bana gelince, bunu yapmanın daha iyi bir yolu, EntityState.Unchangeddeğişiklikleri geri almak istediğiniz her varlığı ayarlamaktır . Bu, değişikliklerin FK'de geri alınmasını ve biraz daha net sözdizimine sahip olmasını sağlar.


4
Not: Varlık yeniden değiştirilirse değişiklikler geri gelecektir.
Nick Whaley

2

Bunun benim bağlamımda iyi çalıştığını buldum:

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


1
Bunun, arama sırasında varlıktaki değişikliklerin devam etmesini engelleyeceğine inanıyorum DbContext.SaveChanges(), ancak varlık değerlerini orijinal değerlere döndürmeyecektir. Ve varlık durumu daha sonraki bir değişiklikten sonra değiştirilirse, muhtemelen önceki tüm değişiklikler kaydedildikten sonra da devam edecek mi?
Carl G

1
Bu bağlantıyı kontrol edin code.msdn.microsoft.com/How-to-undo-the-changes-in-00aed3c4 Bu Unchaged durumuna Bir varlık ayarı "yorganın altında" özgün değerleri geri yükler söylüyor.
Hannish

2

Bu, Mrnka'nın bahsettiği şeyin bir örneğidir. Aşağıdaki yöntem orijinal değerlerine sahip bir işletmenin mevcut değerlerin üzerine yazılır ve yok veritabanını dışarı diyoruz. Bunu, DbEntityEntry öğesinin OriginalValues ​​özelliğini kullanarak yapıyoruz ve değerleri genel bir şekilde ayarlamak için yansımadan yararlanıyoruz. (Bu, EntityFramework 5.0'dan itibaren çalışır)

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

Legacy Object bağlamıyla EF 4 kullanıyoruz. Yukarıdaki çözümlerden hiçbiri bunu benim için doğrudan yanıtlamadı - ancak uzun vadede beni doğru yöne iterek yanıt vermedi.

Hafızada takıldığımız bazı nesneler (bu tembel yüklemeye lanet olsun !!) hala içeriğe bağlı olduğundan, ancak henüz yüklenmemiş çocuklara sahip olduğundan, bağlamı bir kenara atıp yeniden inşa edemeyiz. Bu durumlar için, veritabanını bozmadan ve mevcut bağlantıyı kesmeden her şeyi orijinal değerlerine döndürmemiz gerekir.

Aynı sorun için çözümümüz aşağıdadır:

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

Umarım bu diğerlerine yardım eder.


0

Yukarıdaki bazı iyi fikirler, ICloneable'ı ve ardından basit bir genişletme yöntemini uygulamayı seçtim.

Burada bulundu: C # 'da genel bir listeyi nasıl klonlarım?

Şu şekilde kullanılacak:

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

Bu şekilde, ürün varlıkları listemi klonlayabildim, her öğeye bir indirim uyguladım ve orijinal varlıktaki herhangi bir değişikliği geri alma konusunda endişelenmem gerekmedi. DBContext ile konuşmanıza ve değişiklik yapmanıza veya ChangeTracker ile çalışmanıza gerek yok. EF6'yı tam olarak kullanmadığımı söyleyebilirsiniz, ancak bu çok güzel ve basit bir uygulama ve bir DB isabetini engelliyor. Bunun bir performans etkisi olup olmadığını söyleyemem.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.