前言 🔗
在使用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
未经广泛测试,请自行品鉴用途