梦想博客

C#中反射使用redis hashの特殊姿势

· 740 words · 2 minutes to read
Tags: redis 反射

在使用redis时免不了使用hash方法来对部分数据进行存储,如果我想存入一个class的话在之前我会将key:{id},然后硬编码存入想要的数据

这样操作起来非常麻烦,而且可能还会存在大小写错误的问题

在某天,突发奇想,能不能使用C#中的反射来自动读取字段名称并自动插入和读取?

开干 🔗

/// <summary>
/// 设置整个class的hash
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="entity"></param>
/// <returns></returns>
public async Task SetHash<T>(string key, T entity) where T:class
{
    var hashEntries = new List<HashEntry>();
    var properties = typeof(T).GetProperties();
    foreach (var prop in properties)
    {
        string value = prop.GetValue(entity)?.ToString() ?? string.Empty; 
        hashEntries.Add(new HashEntry(prop.Name, value));
    }
    await _db.HashSetAsync(key, hashEntries.ToArray());
}

/// <summary>
/// 通过class获取整个hash
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public async Task<T> GetHash<T>(string key) where T : new()
{
    var hashEntries = await _db.HashGetAllAsync(key);
    var result = new T();
    var type = typeof(T);
    foreach (var entry in hashEntries)
    {
        var prop = type.GetProperty(entry.Name);
        if (prop != null)
        {
            prop.SetValue(result, ConvertToType(entry.Value, prop.PropertyType));
        }
    }
    return result;
}

/// <summary>
/// 获取hash字段
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TField"></typeparam>
/// <param name="key"></param>
/// <param name="fieldExpression"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public async Task<TField> GetHashValue<T, TField>(string key, Expression<Func<T, TField>> fieldExpression)
{
    if (fieldExpression.Body is MemberExpression memberExpression)
    {
        var fieldName = memberExpression.Member.Name; 
        var redisValue = await _db.HashGetAsync(key, fieldName);

        if (redisValue.IsNull)
        {
            // 如果Redis值为null,返回TField类型的默认值
            return default;  
        }

        return (TField)Convert.ChangeType(redisValue.ToString(), typeof(TField));
    }
    else
    {
        throw new ArgumentException("Invalid field expression. Expression must be a field access expression.", nameof(fieldExpression));
    }
}

private object ConvertToType(RedisValue value, Type targetType)
{
    if (value.IsNull)
    {
        return null;
    }
    if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Nullable<>))
    {
        targetType = Nullable.GetUnderlyingType(targetType);
    }
    return Convert.ChangeType(value, targetType);

使用方法


//插入整个class
var model = new Model.TestModel()
{
    Id = 1,
    Name = "DreamSlave",
    Age = 25
};
var hashKey = $"hash:{model.Id}";
await redis.SetHash(hashKey, model);

//根据主键id获取整个class属性
var model = await redis.GetHash<Model.TestModel>(hashKey);

//根据表达式树泛型获取class下的某个字段属性
var value = await redis.GetHashValue<Model.TestModel, int>(hashKey, x => x.Age);

优势/劣势 🔗

劣势:

  • 因为将整个class存入了redis中,会导致内存占用较高

不支持匿名类也不支持class中嵌套class的情况(感谢micoya奆佬的指示)

优势

  • 在部分情况下减少了其他缓存字段(例如库存)代码如下
//获取库存
var stock =  await redis.GetHashValue<Model.Product, int>(hashKey, x => x.Stock);

//在这里可以直接使用redis提供的hash字段incr和decr
  • 不需要硬编码hash字段,hash字段可以不是字段名而是特性Display/Description

未经广泛测试,请自行品鉴用途

Categories