How to cache an object with multiple keys in C#

c# caching best practices
c# cache data from database
c# lazy cache example
c# cache list of objects
distributed cache c#
server side caching c#
c# disk cache
in-memory cache implementation in c#

I have models that are unique in 2 or more properties. For example, objects of the class Entity are unique both by name and by ID.

public class Entity
{
    public int Id { get; set; }
    public string Name { get; set; }
}

And I have a repository for the model:

public class EntityRepository
{
    ...
    public Entity GetById(int id)
    {
        return db.GetById(id);
    }
    public Entity GetByName(string name)
    {
        return db.GetByName(name);
    }
}

What is the best way to cache both calls to GetById and calls to GetByName using Microsoft.Extensions.Caching.Memory.IMemoryCache?

The current solution:

public class EntityRepository
{
    ...
    public Entity GetById(int id)
    {
        return Cache.GetOrCreate($"id:{id}", cacheEntry =>
        {
            return db.GetById(id);
        });
    }
    public Entity GetByName(string name)
    {
        return Cache.GetOrCreate($"name:{name}", cacheEntry =>
        {
            return db.GetByName(name);
        });
    }
    public void RemoveById(int id)
    {
        db.RemoveById(id);
        Cache.Remove($"id:{id}");
    }
}

The problem here is that if I delete an entity by its ID, I would be able to remove it from the cache via ID, but it would still exist with the other key. And there is a similar problem for updating entities.

Is there a better solution than saving the object twice in the cache?


In your case I would cache in a different way, basically, I would keep the cached entries in a list in my cache, and retrieve the list and search there.

If you think the list will get too big to search into, then you might want to partition a bit for performance reasons.

A general example would be the following.

public class EntityRepository
{

    public Entity GetById(int id)
    {
        List<Entity> entities = Cache.GetOrCreate($"entities", cacheEntry =>
        {
            // Create an empty list of entities
            return new List<Entity>();
        });

        // Look for the entity
        var entity = entities.Where(e => e.id == id).FirstOrDefault();
        // if not there, then add it to the cached list
        if (entity == null)
        {
            entity = db.GetById(id);
            entities.Add(entity)
            // Update the cache
            Cache.Set($"entities", entities);
        }
        return entity;
    }
    public Entity GetByName(string name)
    {
        // Same thing with id    
    }
    public void RemoveById(int id)
    {
        // load the list, remove item and update cache
    }
}

In any other case, you need to wrap around your implementation, with some kind of logic. Maybe multi key dictionaries, or some kind of datastructure to retain history and do custom cleanups. There is nothing out of the box though.

You can also simplify code and repeating, by extracting the list of entities into a getter and setter like this:

public List<Entity> Entities
{
    get { return Cache.GetOrCreate($"entities", cacheEntry =>
                 {
                     // Create an empty list of entities
                     return new List<Entity>();
                 }); 
    }
    set { Cache.Set($"entities", value); }
}

Optimize cache with multiple keys in c#, Optimize cache with multiple keys in c# - remove duplication of objects. c# cache c# disk cache c# caching best practices thread safe cache c# c# lazy cache� The first parameter is the key of the cache entry. The second parameter is the value of the cache entry. The third parameter is the cache item policy of the cache entry. Here is an example of how to add data in the memory cache. var result = cache.Add ("fullName", "Jaimin Shethiya", cacheItemPolicy);


Memory Cache stores cache items as key-value pairs, so we can take this to our advantage.

so instead of doing this :

public Entity GetById(int id)
{
    return Cache.GetOrCreate($"id:{id}", cacheEntry =>
    {
        return db.GetById(id);
    });
}

you could do something like this :

public Entity GetById(int id)
{
    string _name = string.Empty;

    if (!_cache.TryGetValue(id, out _name))
    {
        var _entity = db.GetById(id);

        _cache.Set(_entity.Id, _entity.Name);
    }

    return _cache.Get<Entity>(id);
}

on get, it'll check the cache if not exists, then it'll get the values from the source and store it in the cache, then it'll return the cache.

now since you need also to check the values and not the keys on the GetByName method, you could cast the cache to a dictionary, and use Linq to retrieve the key by its value.

    public Entity GetByName(string name)
    {
        int id;

        var dic = _cache as IDictionary<int, string>;

        if (!dic.Values.Contains(name))
        {
            var _entity = db.GetByName(name);

            _cache.Set(_entity.Id, _entity.Name);

            id = _entity.Id;
        }
        else
        {
            id = dic.FirstOrDefault(x => x.Value == name).Key;
        }

        return _cache.Get<Entity>(id);
    }

now when you use RemoveById you just pass the id :

public void RemoveById(int id)
{
    db.RemoveById(id);
    _cache.Remove(id);
}

I have not tested the above code, I just wanted to give you some insights that could lead you to your desired results.

Cache Implementations in C# .NET, public TItem GetOrCreate(object key, Func<TItem> createItem). {. if (!_cache. There's no danger of multiple threads accessing the same cache item. You don't � When overridden in a derived class, inserts a cache entry into the cache. Set (String, Object, Date Time Offset, String) When overridden in a derived class, inserts a cache entry into the cache, specifying time-based expiration details. To String () Returns a string that represents the current object.


Not sure if there is a better solution than storing object twice in cache but there is a simple solution for deleting/updating. First, get the an object by Id and then remove all its cache entries by known properties values

public class EntityRepository
{
    public Entity GetById(int id)
    {
        return Cache.GetOrCreate($"id:{id}", cacheEntry =>
        {
            return db.GetById(id);
        });
    }
    public Entity GetByName(string name)
    {
        return Cache.GetOrCreate($"name:{name}", cacheEntry =>
        {
            return db.GetByName(name);
        });
    }
    public void RemoveById(int id)
    {
        db.RemoveById(id);
        if (Cache.TryGetValue(id, out Entity entity))
        {
            Cache.Remove($"id:{entity.Id}");
            Cache.Remove($"name:{entity.Name}");
        }
    }
}

A Simple Way to Cache Objects and Collections for Greater , Implement caching in your project using AspectF library that saves you from writing Handling Objects that are Cached with Multiple Keys. Deletion of objects from cache based on key, or object type Querying cache store to check existence of an object There are several ways to achieve this using multiple design patterns.


Memory Cache in C#, This is used for a shared cache and multiple processes. We also use the bool Add(string key, object value, CacheItemPolicy policy). The first� Storing Same Item using Multiple Keys. Sometimes you store objects in cache using the key and also by index because you not only need to retrieve items by key but also need to iterate through items using index. For example, var someItem = new SomeClass(); myCache[" SomeKey"] = someItem; . . myCache[" SomeItem." + index] = someItem; . .


MemoryCache.Set Method (System.Runtime.Caching), Set(String, Object, CacheItemPolicy, String). Inserts a cache entry into the cache by using a key and a value and eviction. C# Set (String, Object, Cache Item Policy, String) Inserts a cache entry into the cache by using a key and a value and eviction. Set (String, Object, Date Time Offset, String) Inserts a cache entry into the cache by using a key and a value and specifies time-based expiration details.


Cache.Insert Method (System.Web.Caching), Inserts an object into the Cache that has file or key dependencies. Inserts an item into the Cache object with a cache key to reference its location, using default C# Copy. public void Insert (string key, object value, System.Web.Caching. Adding an item to the cache by directly setting the item via key and value. Adding items to the cache using the Insert method. Adding an item to the cache and adding a dependency so that the item is removed from the cache when the dependency changes. You can set dependencies based on other cache items, on files, and on multiple objects.