Reputation: 930
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?
Upvotes: 7
Views: 9113
Reputation: 9642
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}");
}
}
}
Upvotes: 0
Reputation: 3498
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.
Upvotes: 0
Reputation: 26450
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); }
}
Upvotes: 0