梦想博客

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

calendar 2023/10/30
refresh-cw 2023/10/31
804字,2分钟
tag redis; 反射;

前言 🔗

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

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

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

开干 🔗

 1/// <summary>
 2/// 设置整个class的hash
 3/// </summary>
 4/// <typeparam name="T"></typeparam>
 5/// <param name="key"></param>
 6/// <param name="entity"></param>
 7/// <returns></returns>
 8public async Task SetHash<T>(string key, T entity) where T:class
 9{
10    var hashEntries = new List<HashEntry>();
11    var properties = typeof(T).GetProperties();
12    foreach (var prop in properties)
13    {
14        string value = prop.GetValue(entity)?.ToString() ?? string.Empty; 
15        hashEntries.Add(new HashEntry(prop.Name, value));
16    }
17    await _db.HashSetAsync(key, hashEntries.ToArray());
18}
19
20/// <summary>
21/// 通过class获取整个hash
22/// </summary>
23/// <typeparam name="T"></typeparam>
24/// <param name="key"></param>
25/// <returns></returns>
26public async Task<T> GetHash<T>(string key) where T : new()
27{
28    var hashEntries = await _db.HashGetAllAsync(key);
29    var result = new T();
30    var type = typeof(T);
31    foreach (var entry in hashEntries)
32    {
33        var prop = type.GetProperty(entry.Name);
34        if (prop != null)
35        {
36            prop.SetValue(result, ConvertToType(entry.Value, prop.PropertyType));
37        }
38    }
39    return result;
40}
41
42/// <summary>
43/// 获取hash字段
44/// </summary>
45/// <typeparam name="T"></typeparam>
46/// <typeparam name="TField"></typeparam>
47/// <param name="key"></param>
48/// <param name="fieldExpression"></param>
49/// <returns></returns>
50/// <exception cref="ArgumentException"></exception>
51public async Task<TField> GetHashValue<T, TField>(string key, Expression<Func<T, TField>> fieldExpression)
52{
53    if (fieldExpression.Body is MemberExpression memberExpression)
54    {
55        var fieldName = memberExpression.Member.Name; 
56        var redisValue = await _db.HashGetAsync(key, fieldName);
57
58        if (redisValue.IsNull)
59        {
60            // 如果Redis值为null,返回TField类型的默认值
61            return default;  
62        }
63
64        return (TField)Convert.ChangeType(redisValue.ToString(), typeof(TField));
65    }
66    else
67    {
68        throw new ArgumentException("Invalid field expression. Expression must be a field access expression.", nameof(fieldExpression));
69    }
70}
71
72private object ConvertToType(RedisValue value, Type targetType)
73{
74    if (value.IsNull)
75    {
76        return null;
77    }
78    if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Nullable<>))
79    {
80        targetType = Nullable.GetUnderlyingType(targetType);
81    }
82    return Convert.ChangeType(value, targetType);

使用方法

 1
 2//插入整个class
 3var model = new Model.TestModel()
 4{
 5    Id = 1,
 6    Name = "DreamSlave",
 7    Age = 25
 8};
 9var hashKey = $"hash:{model.Id}";
10await redis.SetHash(hashKey, model);
11
12//根据主键id获取整个class属性
13var model = await redis.GetHash<Model.TestModel>(hashKey);
14
15//根据表达式树泛型获取class下的某个字段属性
16var value = await redis.GetHashValue<Model.TestModel, int>(hashKey, x => x.Age);

优势/劣势 🔗

劣势:

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

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

优势

  • 在部分情况下减少了其他缓存字段(例如库存)代码如下
1//获取库存
2var stock =  await redis.GetHashValue<Model.Product, int>(hashKey, x => x.Stock);
3
4//在这里可以直接使用redis提供的hash字段incr和decr
  • 不需要硬编码hash字段,hash字段可以不是字段名而是特性Display/Description

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

分类