2012-12-18 7 views
12

Obecnie próbuję nowego MemoryCache w .Net 4, aby buforować kilka bitów danych w jednej z naszych aplikacji. Problem polega na tym, że obiekty są aktualizowane, a pamięć podręczna wydaje się utrwalać zmiany, np.Jak mogę odłączyć odniesienie do obiektu w MemoryCache?

public IEnumerable<SomeObject> GetFromDatabase(){ 
    const string _cacheKeyGetDisplayTree = "SomeKey"; 
    ObjectCache _cache = MemoryCache.Default; 
    var objectInCache = _cache.Get(_cacheKeyGetDisplayTree) as IEnumerable<SomeObject>; 
    if (objectInCache != null) 
     return objectInCache.ToList(); 

    // Do something to get the items 
    _cache.Add(_cacheKeyGetDisplayTree, categories, new DateTimeOffset(DateTime.UtcNow.AddHours(1))); 

    return categories.ToList(); 
} 

public IEnumerable<SomeObject> GetWithIndentation(){ 
    var categories = GetFromDatabase(); 

    foreach (var c in categories) 
    { 
     c.Name = "-" + c.Name; 
    } 

    return categories; 
} 

Gdybym nazywając GetWithIndentation() najpierw a potem dzwoni GetFromDatabase() Spodziewam się, że aby przywrócić pierwotną listę SomeObject lecz zwraca zmodyfikowanych elementów (z „-” prefiksem imienia).

Pomyślałem, że ToList() zniszczył odniesienie, ale nadal wydaje się, że utrzymuje zmiany. Jestem pewien, że to oczywiste, ale czy ktoś może zauważyć, gdzie się nie mylę?

+4

Robisz kopię kolekcji, ale nie obiekty wewnątrz niego. –

+0

Ah dobre miejsce dzięki. Zastanawiam się teraz, jak najlepiej to zrobić, czy jest to głęboka kopia, czy też jest sposób, aby MemoryCache ignorować kolejne zmiany? – Tim

+1

Zmodyfikuj go przed buforowaniem lub nigdy. Poza tym jest to tak banalna zmiana, prawdopodobnie powinna być wykonana w warstwie prezentacji. –

Odpowiedz

0

W pamięci podręcznej obiektów są przechowywane w tym samym obszarze procesowym, co proces klienta pamięci podręcznej. Gdy klient pamięci podręcznej żąda obiektu buforowanego, klient otrzymuje odwołanie do obiektu buforowanego lokalnie, a nie do kopii.

Jedynym sposobem na uzyskanie czystej kopii obiektu jest wdrożenie niestandardowego mechanizmu klonowania (ICloneable, Serialization, Automapping, ...). Dzięki tej kopii będziesz mógł zmienić nowy obiekt bez zmiany obiektu nadrzędnego.

Zależnie od przypadku użycia, zazwyczaj nie zaleca się aktualizowania obiektu w pamięci podręcznej.

+0

"W zależności od przypadku użycia, zazwyczaj nie zaleca się aktualizowania obiektu w pamięci podręcznej." Czy zastanawiasz się, dlaczego tak się dzieje? Jako prosty przykład powiedzmy, że masz listę użytkowników przechowywanych w pamięci podręcznej. Użytkownicy rzadko zmieniają się, ale w pewnym momencie ulegną zmianie (np. Zaktualizują imię/nazwisko, adres itd.) - czy to byłoby dobrym kandydatem do przechowywania zbioru użytkowników w pamięci podręcznej i aktualizowania ich w razie potrzeby? –

8

Utworzono klasę ReadonlyMemoryCache w celu rozwiązania tego problemu. Dziedziczy po MemoryCache .NET 4.0, ale obiekty są przechowywane tylko do odczytu (wartość by-value) i nie można ich modyfikować. Głęboko kopiuję obiekty przed przechowywaniem przy użyciu serializacji binarnej.

using System; 
using System.Collections.Generic; 
using System.Collections.Specialized; 
using System.IO; 
using System.Runtime.Caching; 
using System.Runtime.Serialization.Formatters.Binary; 
using System.Threading.Tasks; 


namespace ReadOnlyCache 
{ 
    class Program 
    { 

     static void Main() 
     { 
      Start(); 
      Console.ReadLine(); 
     } 

     private static async void Start() { 
      while (true) 
      { 
       TestMemoryCache(); 
       await Task.Delay(TimeSpan.FromSeconds(1)); 
      } 
     } 

     private static void TestMemoryCache() { 
      List<Item> items = null; 
      string cacheIdentifier = "items"; 

      var cache = ReadonlyMemoryCache.Default; 

      //change to MemoryCache to understand the problem 
      //var cache = MemoryCache.Default; 

      if (cache.Contains(cacheIdentifier)) 
      { 
       items = cache.Get(cacheIdentifier) as List<Item>; 
       Console.WriteLine("Got {0} items from cache: {1}", items.Count, string.Join(", ", items)); 

       //modify after getting from cache, cached items will remain unchanged 
       items[0].Value = DateTime.Now.Millisecond.ToString(); 

      } 
      if (items == null) 
      { 
       items = new List<Item>() { new Item() { Value = "Steve" }, new Item() { Value = "Lisa" }, new Item() { Value = "Bob" } }; 
       Console.WriteLine("Reading {0} items from disk and caching", items.Count); 

       //cache for x seconds 
       var policy = new CacheItemPolicy() { AbsoluteExpiration = new DateTimeOffset(DateTime.Now.AddSeconds(5)) }; 
       cache.Add(cacheIdentifier, items, policy); 

       //modify after writing to cache, cached items will remain unchanged 
       items[1].Value = DateTime.Now.Millisecond.ToString(); 
      } 
     } 
    } 

    //cached items must be serializable 

    [Serializable] 
    class Item { 
     public string Value { get; set; } 
     public override string ToString() { return Value; } 
    } 

    /// <summary> 
    /// Readonly version of MemoryCache. Objects will always be returned in-value, via a deep copy. 
    /// Objects requrements: [Serializable] and sometimes have a deserialization constructor (see http://stackoverflow.com/a/5017346/2440) 
    /// </summary> 
    public class ReadonlyMemoryCache : MemoryCache 
    { 

     public ReadonlyMemoryCache(string name, NameValueCollection config = null) : base(name, config) { 
     } 

     private static ReadonlyMemoryCache def = new ReadonlyMemoryCache("readonlydefault"); 

     public new static ReadonlyMemoryCache Default { 
      get 
      { 
       if (def == null) 
        def = new ReadonlyMemoryCache("readonlydefault"); 
       return def; 
      } 
     } 

     //we must run deepcopy when adding, otherwise items can be changed after the add() but before the get() 

     public new bool Add(CacheItem item, CacheItemPolicy policy) 
     { 
      return base.Add(item.DeepCopy(), policy); 
     } 

     public new object AddOrGetExisting(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null) 
     { 
      return base.AddOrGetExisting(key, value.DeepCopy(), absoluteExpiration, regionName); 
     } 

     public new CacheItem AddOrGetExisting(CacheItem item, CacheItemPolicy policy) 
     { 
      return base.AddOrGetExisting(item.DeepCopy(), policy); 
     } 

     public new object AddOrGetExisting(string key, object value, CacheItemPolicy policy, string regionName = null) 
     { 
      return base.AddOrGetExisting(key, value.DeepCopy(), policy, regionName); 
     } 

     //methods from ObjectCache 

     public new bool Add(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null) 
     { 
      return base.Add(key, value.DeepCopy(), absoluteExpiration, regionName); 
     } 

     public new bool Add(string key, object value, CacheItemPolicy policy, string regionName = null) 
     { 
      return base.Add(key, value.DeepCopy(), policy, regionName); 
     } 

     //for unknown reasons, we also need deepcopy when GETTING values, even though we run deepcopy on all (??) set methods. 

     public new object Get(string key, string regionName = null) 
     { 
      var item = base.Get(key, regionName); 
      return item.DeepCopy(); 
     } 

     public new CacheItem GetCacheItem(string key, string regionName = null) 
     { 
      var item = base.GetCacheItem(key, regionName); 
      return item.DeepCopy(); 
     } 

    } 


    public static class DeepCopyExtentionMethods 
    { 
     /// <summary> 
     /// Creates a deep copy of an object. Must be [Serializable] and sometimes have a deserialization constructor (see http://stackoverflow.com/a/5017346/2440) 
     /// </summary> 
     public static T DeepCopy<T>(this T obj) 
     { 
      using (var ms = new MemoryStream()) 
      { 
       var formatter = new BinaryFormatter(); 
       formatter.Serialize(ms, obj); 
       ms.Position = 0; 

       return (T)formatter.Deserialize(ms); 
      } 
     } 
    } 



} 
+0

Ten wygląda jak zwycięzca, klasa DeepCopyExtensionMethods rozwiązuje problem. – user917170

+0

Nice!Nie jestem pewien, dlaczego wywoływanie "DeepCopy" w metodach "Get" byłoby konieczne, ale może coś zmienić. Jeśli obiekt buforowany został zaktualizowany w innym miejscu, klonowanie go w metodzie "Get" jest za późno ... dane w nim zawarte zostały już pomieszane. –

+0

@GrantWinney Jeśli usuniesz deepcopy z Get(), zobaczysz, że to nie działa. Zgadzam się, że nie powinno to być konieczne. – Sire

0

Dlaczego nie przechowywać jako json lub ciąg? Nie są one przekazywane przez odniesienie, a kiedy wyjdziesz z pamięci podręcznej otrzymasz nową kopię :) Jestem tutaj, aby zostać zakwestionowanym, ponieważ to robię atm!

+0

Ponieważ zapłaciłbyś koszt serializacji przy każdym Get – TheLazyDogsBack

0

Możesz to zrobić łatwiej, jeśli ponownie sformatujesz i serializujesz ponownie, a otrzymasz obiekt pamięci podręcznej "Według wartości".

Można zrobić to w ten sposób z Newtonsoft lib (tylko dostać od Nuget)

var cacheObj = HttpRuntime.Cache.Get(CACHEKEY); 
var json = JsonConvert.SerializeObject(cacheObj); 
var byValueObj = JsonConvert.DeserializeObject<List<string>>(json); 
return byValueObj; 
Powiązane problemy