简体   繁体   English

如何在C#中缓存具有多个键的对象

[英]How to cache an object with multiple keys in C#

I have models that are unique in 2 or more properties.我的模型在 2 个或更多属性中是唯一的。 For example, objects of the class Entity are unique both by name and by ID.例如, Entity类的对象在名称和 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 ?使用Microsoft.Extensions.Caching.Memory.IMemoryCache缓存对GetById调用和对GetByName调用的最佳方法是什么?

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.这里的问题是,如果我通过 ID 删除一个实体,我将能够通过 ID 从缓存中删除它,但它仍然会与另一个键一起存在。 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:您还可以通过将实体列表提取到 getter 和 setter 中来简化代码和重复,如下所示:

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

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.现在,由于您还需要检查GetByName方法上的值而不是键,您可以将缓存转换为字典,并使用Linq通过其值检索键。

    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 :现在,当您使用RemoveById您只需传递 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.我没有测试上面的代码,我只是想给你一些见解,可以引导你达到你想要的结果。

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首先,通过Id获取一个对象,然后通过已知的属性值删除其所有缓存条目

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM