基于索引提取JSON属性并优化MongoDB存储访问性能问题
高效解决动态属性映射与序列化兼容问题
针对你遇到的问题——既要高效从Product关联动态Properties,又要兼容MongoDB和WCF序列化,同时处理大体积JSON的内存友好性——这里有几个优化方案,完全避开ExpandoObject的兼容性坑:
方案1:预缓存Properties为列表,结合延迟迭代器(推荐)
核心思路是只转换一次OrderedDictionary为可快速访问的列表,之后通过索引直接获取属性,同时用迭代器yield return实现内存友好的延迟加载,完美兼容MongoDB和WCF。
步骤1:修改Entity类,添加缓存列表
在反序列化完成后,把OrderedDictionary转换成List<Dictionary<string, object>>缓存起来,避免每次访问都做Cast和ElementAt(后者是O(n)操作,大数据下极慢):
using System.Collections.Specialized; using System.Runtime.Serialization; using Newtonsoft.Json; public class Entity { // 保留原始的OrderedDictionary用于反序列化 [JsonProperty("Properties")] public OrderedDictionary RawProperties { get; set; } // 缓存转换后的Properties列表,反序列化后自动初始化 [JsonIgnore] public List<Dictionary<string, object>> PropertiesCache { get; private set; } public IEnumerable<Product> Products { get; set; } // 反序列化完成后触发,初始化缓存 [OnDeserialized] private void OnDeserialized(StreamingContext context) { PropertiesCache = RawProperties? .Cast<DictionaryEntry>() .Select(entry => new Dictionary<string, object> { { entry.Key.ToString(), entry.Value } }) .ToList() ?? new List<Dictionary<string, object>>(); } }
步骤2:修改Product类,实现高效的PropertiesData
用迭代器返回关联的属性数据,既不会一次性加载所有数据到内存,又能直接被MongoDB和WCF序列化:
using System.Collections.Generic; using Newtonsoft.Json; public class Product { [JsonIgnore] public Entity Entity { get; set; } public int Id { get; set; } public int[] PropertiesIndexes { get; set; } // 这个属性会被Json.NET正确序列化,MongoDB和WCF也能识别 [JsonProperty("PropertiesData")] public IEnumerable<Dictionary<string, object>> PropertiesData { get { if (Entity?.PropertiesCache == null || PropertiesIndexes == null) yield break; foreach (var index in PropertiesIndexes) { // 边界检查避免索引越界 if (index >= 0 && index < Entity.PropertiesCache.Count) yield return Entity.PropertiesCache[index]; } } } }
优势
- 性能提升:缓存列表后,索引访问是O(1)操作,彻底解决原实现中
ElementAt的性能瓶颈 - 内存友好:迭代器
yield return只会在序列化/遍历的时候逐个加载属性,不会一次性占用大量内存 - 兼容性强:
Dictionary<string, object>是MongoDB原生支持的类型,WCF添加[DataContract]/[DataMember]标记后也能正常序列化:[DataContract] public class Product { [DataMember] public int Id { get; set; } [DataMember] public IEnumerable<Dictionary<string, object>> PropertiesData { get; set; } // 其他属性... }
方案2:自定义JsonConverter,序列化时动态生成PropertiesData
如果不想在Entity中缓存数据(比如极端大文件场景),可以用Json.NET的自定义转换器,在序列化Product时直接从原始OrderedDictionary中提取数据:
using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.Collections.Specialized; public class ProductConverter : JsonConverter<Product> { public override void WriteJson(JsonWriter writer, Product value, JsonSerializer serializer) { writer.WriteStartObject(); // 序列化Id writer.WritePropertyName("Id"); serializer.Serialize(writer, value.Id); // 序列化PropertiesData writer.WritePropertyName("PropertiesData"); writer.WriteStartArray(); if (value.Entity?.RawProperties != null && value.PropertiesIndexes != null) { foreach (var index in value.PropertiesIndexes) { if (index >= 0 && index < value.Entity.RawProperties.Count) { var entry = value.Entity.RawProperties.Cast<DictionaryEntry>().ElementAt(index); var propDict = new Dictionary<string, object> { { entry.Key.ToString(), entry.Value } }; serializer.Serialize(writer, propDict); } } } writer.WriteEndArray(); writer.WriteEndObject(); } public override Product ReadJson(JsonReader reader, Type objectType, Product existingValue, bool hasExistingValue, JsonSerializer serializer) { // 反序列化逻辑:如果是从目标JSON(带PropertiesData)反序列化,这里可以反向生成PropertiesIndexes // 若还是从原始JSON反序列化,直接按原逻辑处理即可 var jObj = JObject.Load(reader); var product = new Product { Id = jObj["Id"].Value<int>() // 按需添加PropertiesIndexes的生成逻辑 }; return product; } }
然后给Product类标记转换器:
[JsonConverter(typeof(ProductConverter))] public class Product { // 原属性... }
优势
- 无需缓存,节省内存空间
- 序列化时动态生成数据,适合超大规模JSON文件处理
为什么这两个方案比原实现更好?
- 性能:原实现每次访问
PropertiesData都要重复Cast和ElementAt,而缓存列表后是O(1)访问,性能提升显著 - 内存:迭代器避免一次性加载所有属性数据,适合数百MB的大文件
- 兼容性:完全避开
ExpandoObject的序列化问题,Dictionary<string, object>是通用兼容类型
内容的提问来源于stack exchange,提问作者thomasb
相关产品推荐
相关产品推荐

