Comment puis-je détacher la référence de l’object sur MemoryCache

J’essaie actuellement le nouveau MemoryCache dans .Net 4 pour mettre en cache quelques bits de données dans l’une de nos applications. Le problème que j’ai est que les objects sont mis à jour et le cache semble persister les modifications, par exemple

 public IEnumerable GetFromDatabase(){ const ssortingng _cacheKeyGetDisplayTree = "SomeKey"; ObjectCache _cache = MemoryCache.Default; var objectInCache = _cache.Get(_cacheKeyGetDisplayTree) as IEnumerable; 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 GetWithIndentation(){ var categories = GetFromDatabase(); foreach (var c in categories) { c.Name = "-" + c.Name; } return categories; } 

Si GetWithIndentation() abord GetWithIndentation() , puis GetFromDatabase() je m’attendrais à ce qu’il renvoie la liste d’origine de SomeObject mais les éléments modifiés (avec le préfixe “-” sur le nom).

Je pensais que ToList() détruit la référence, mais il semble que les changements persistent. Je suis sûr que c’est évident, mais est-ce que quelqu’un peut voir où je me trompe?

J’ai créé une classe ReadonlyMemoryCache pour résoudre ce problème. Il hérite de .NET 4.0 MemoryCache, mais les objects sont stockés en lecture seule (par valeur) et ne peuvent pas être modifiés. Je copie en profondeur les objects avant de les stocker en utilisant la sérialisation binary.

 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 items = null; ssortingng 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; Console.WriteLine("Got {0} items from cache: {1}", items.Count, ssortingng.Join(", ", items)); //modify after getting from cache, cached items will remain unchanged items[0].Value = DateTime.Now.Millisecond.ToSsortingng(); } if (items == null) { items = new List() { 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.ToSsortingng(); } } } //cached items must be serializable [Serializable] class Item { public ssortingng Value { get; set; } public override ssortingng ToSsortingng() { return Value; } } ///  /// 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) ///  public class ReadonlyMemoryCache : MemoryCache { public ReadonlyMemoryCache(ssortingng 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(ssortingng key, object value, DateTimeOffset absoluteExpiration, ssortingng 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(ssortingng key, object value, CacheItemPolicy policy, ssortingng regionName = null) { return base.AddOrGetExisting(key, value.DeepCopy(), policy, regionName); } //methods from ObjectCache public new bool Add(ssortingng key, object value, DateTimeOffset absoluteExpiration, ssortingng regionName = null) { return base.Add(key, value.DeepCopy(), absoluteExpiration, regionName); } public new bool Add(ssortingng key, object value, CacheItemPolicy policy, ssortingng 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(ssortingng key, ssortingng regionName = null) { var item = base.Get(key, regionName); return item.DeepCopy(); } public new CacheItem GetCacheItem(ssortingng key, ssortingng regionName = null) { var item = base.GetCacheItem(key, regionName); return item.DeepCopy(); } } public static class DeepCopyExtentionMethods { ///  /// Creates a deep copy of an object. Must be [Serializable] and sometimes have a deserialization constructor (see http://stackoverflow.com/a/5017346/2440) ///  public static T DeepCopy(this T obj) { using (var ms = new MemoryStream()) { var formatter = new BinaryFormatter(); formatter.Serialize(ms, obj); ms.Position = 0; return (T)formatter.Deserialize(ms); } } } } 

En mémoire, les objects mis en cache sont stockés dans le même espace de processus que le processus du client cache. Lorsqu’un client en cache demande un object mis en cache, le client reçoit une référence à l’object mis en cache localement plutôt qu’une copie.

Le seul moyen d’obtenir une copie vierge de l’object consiste à implémenter un mécanisme de clonage personnalisé (ICloneable, Serialization, Automapping, …). Avec cette copie, vous pourrez modifier le nouvel object sans modifier l’object parent.

En fonction de votre cas d’utilisation, il n’est généralement pas recommandé de mettre à jour un object dans le cache.

Pourquoi ne pas simplement stocker comme json ou une chaîne? Celles-ci ne sont pas transmises par référence et lorsque vous sortez de la mémoire cache, vous obtenez une nouvelle copie 🙂 Je suis ici pour être mis au défi car c’est ce que je fais avec atm!

Vous pouvez le faire plus facilement si vous désérialisez et serrez à nouveau et obtenez votre object de cache “Par valeur”.

Vous pouvez le faire comme ceci avec Newtonsoft lib (procurez-vous-le auprès de NuGet)

 var cacheObj = HttpRuntime.Cache.Get(CACHEKEY); var json = JsonConvert.SerializeObject(cacheObj); var byValueObj = JsonConvert.DeserializeObject>(json); return byValueObj; 

La sérialisation / désérialisation résoudra le problème mais en même temps, elle empêchera les objects d’avoir en mémoire. Le rôle du cache est de fournir un access rapide à l’object stocké et nous ajoutons ici la surcharge de désérialisation. Étant donné que la désérialisation est requirejse, je suggérerais le cache en tant que service, un peu comme le cache redis, il sera centralisé afin que vous n’ayez pas besoin de copie d’object mémoire par processus de travail et que la désérialisation soit quand même effectuée.

Dans ce cas, l’essentiel est que vous ayez choisi une option de sérialisation / désérialisation rapide.