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