Redis批量读写时HashSet类型反序列化失败求助
场景说明
使用以下.NET代码实现Redis的批量写入与读取,其中Dictionary<TKey, TValue>实际为Dictionary<ClassId, HashSet<Security>>。数据已成功写入Redis,示例Key为<keyPrefix>:<SomeId>,Value为[{"Name":"Test"}],但执行批量读取方法时反序列化失败。
相关代码
public void BulkSet(Dictionary<TKey, TValue> keyValues) { if (keyValues == null || keyValues.Count == 0) return; var batch = _db.CreateBatch(); foreach (var item in keyValues) { var json = JsonConvert.SerializeObject(item.Value); batch.JsonSetAsync(ToRedisKey(item.Key), json, "$"); } batch.Execute(); } private string ToRedisKey([DisallowNull] TKey key) { if (key is ICacheKey cacheKey) return $"{_repositoryConfig.RedisRepositoryConfig.KeyPrefix}:" + cacheKey.GenerateKey(); else if (key is string str) return $"{_repositoryConfig.RedisRepositoryConfig.KeyPrefix}:{str}"; else return $"{_repositoryConfig.RedisRepositoryConfig.KeyPrefix}:{key.ToString().ToLower()}"; } private void BulkGetUsingPipelining(HashSet<TKey> keys, Dictionary<TKey, TValue> cachedItems, HashSet<TKey> nonCachedKeys) { var orderedKeys = keys.ToArray(); var batch = _db.CreateBatch(); var tasks = orderedKeys.Select(k => batch.JsonGetAsync(ToRedisKey(k))).ToList(); batch.Execute(); Task.WhenAll(tasks); for (var i = 0; i < tasks.Count; i++) { var result = tasks[i].Result; var key = orderedKeys.ElementAt(i); TValue value; if (!result.IsNull) { if (result.ToString().TrimStart().StartsWith('[')) { value = JsonConvert.DeserializeObject<HashSet<TValue>>(result.ToString())!.First(); // I tried with List also //value = JsonConvert.DeserializeObject<List<TValue>>(result.ToString())!.First(); } else value = JsonConvert.DeserializeObject<TValue>(result.ToString())!; cachedItems.Add(key, value); } else nonCachedKeys.Add(key); } }
报错信息
无法将当前JSON对象(例如{"name":"value"})反序列化为类型'System.Collections.Generic.HashSet`1[Digital.MarketData.Security]',因为该类型需要JSON数组(例如[1,2,3])才能正确反序列化。要解决此错误,可将JSON改为JSON数组,或更改被反序列化的类型为可从JSON对象反序列化的常规.NET类型(例如非整数等原始类型、非数组或List等集合类型)。也可向该类型添加JsonObjectAttribute以强制其从JSON对象反序列化。路径'PpaSecurityId',第1行,第17位。
问题原因与解决方案
原因分析
写入Redis时,item.Value是HashSet<Security>,序列化后得到的JSON是Security对象的数组。但读取时,错误地将JSON数组反序列化为HashSet<TValue>(即HashSet<HashSet<Security>>),而JSON数组中的元素是单个Security对象,并非HashSet,因此触发反序列化错误。
修改方案
直接将JSON数组反序列化为TValue(也就是HashSet<Security>)即可,不需要额外套一层集合再取First()。修改后的BulkGetUsingPipelining方法如下:
private void BulkGetUsingPipelining(HashSet<TKey> keys, Dictionary<TKey, TValue> cachedItems, HashSet<TKey> nonCachedKeys) { var orderedKeys = keys.ToArray(); var batch = _db.CreateBatch(); var tasks = orderedKeys.Select(k => batch.JsonGetAsync(ToRedisKey(k))).ToList(); batch.Execute(); // 确保等待所有异步任务完成,避免读取未完成的结果 Task.WhenAll(tasks).Wait(); for (var i = 0; i < tasks.Count; i++) { var result = tasks[i].Result; var key = orderedKeys[i]; // 直接索引访问更高效 if (!result.IsNull) { // 直接反序列化为TValue,与写入时的序列化类型保持一致 var value = JsonConvert.DeserializeObject<TValue>(result.ToString())!; cachedItems.Add(key, value); } else { nonCachedKeys.Add(key); } } }
额外优化点
- 原代码中
Task.WhenAll(tasks);未等待任务完成,改为Task.WhenAll(tasks).Wait();确保所有读取任务执行完毕;如果方法允许异步,更推荐改为async Task方法并使用await Task.WhenAll(tasks);。 - 用
orderedKeys[i]替代orderedKeys.ElementAt(i),索引访问效率更高。
内容的提问来源于stack exchange,提问作者ANEES

