使用C# MongoDB驱动存储含Dictionary<DateTime,T>的对象遇问题求助
解决C# MongoDB驱动中Dictionary<DateTime, T>的通用序列化问题
1. 自定义通用Dictionary<DateTime, T>序列化器
创建一个继承DictionarySerializerBase的序列化器,专门处理键为DateTime的字典,将DateTime键转换为MongoDB支持的类型,同时兼容任意值类型T:
public class DateTimeKeyDictionarySerializer<TValue> : DictionarySerializerBase<Dictionary<DateTime, TValue>, KeyValuePair<DateTime, TValue>> { private readonly IBsonSerializer<TValue> _valueSerializer; public DateTimeKeyDictionarySerializer() : this(BsonSerializer.LookupSerializer<TValue>()) { } public DateTimeKeyDictionarySerializer(IBsonSerializer<TValue> valueSerializer) { _valueSerializer = valueSerializer ?? throw new ArgumentNullException(nameof(valueSerializer)); } protected override Dictionary<DateTime, TValue> CreateInstance() { return new Dictionary<DateTime, TValue>(); } protected override void AddItem(Dictionary<DateTime, TValue> dictionary, KeyValuePair<DateTime, TValue> item) { dictionary.Add(item.Key, item.Value); } public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, Dictionary<DateTime, TValue> value) { var writer = context.Writer; writer.WriteStartDocument(); foreach (var kvp in value) { // 将DateTime转为BsonDateTime后作为键名,MongoDB可直接识别 writer.WriteName(BsonDateTime.Create(kvp.Key).ToString()); _valueSerializer.Serialize(context, kvp.Value); } writer.WriteEndDocument(); } public override Dictionary<DateTime, TValue> Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) { var reader = context.Reader; var dictionary = CreateInstance(); reader.ReadStartDocument(); while (reader.ReadBsonType() != BsonType.EndOfDocument) { var keyString = reader.ReadName(); if (BsonDateTime.TryParse(keyString, out var bsonDate)) { var value = _valueSerializer.Deserialize(context); // 根据项目时区需求调整,这里转为UTC时间 dictionary.Add(bsonDate.ToUniversalTime(), value); } else { reader.SkipValue(); // 跳过无效键值对 } } reader.ReadEndDocument(); return dictionary; } }
2. 通过约定全局注册序列化器
创建自定义约定,让所有Dictionary<DateTime, T>类型自动绑定上面的序列化器,无需逐个属性添加标签:
public class DateTimeKeyDictionaryConvention : ConventionBase, IMemberMapConvention { public void Apply(BsonMemberMap memberMap) { var memberType = memberMap.MemberType; // 判断属性是否为Dictionary<DateTime, T>泛型类型 if (memberType.IsGenericType && memberType.GetGenericTypeDefinition() == typeof(Dictionary<,>) && memberType.GetGenericArguments()[0] == typeof(DateTime)) { var valueType = memberType.GetGenericArguments()[1]; // 动态创建对应值类型的序列化器实例 var serializerType = typeof(DateTimeKeyDictionarySerializer<>).MakeGenericType(valueType); var serializer = (IBsonSerializer)Activator.CreateInstance(serializerType); memberMap.SetSerializer(serializer); } } } // 在MongoDB初始化流程中注册约定 var conventionPack = new ConventionPack(); conventionPack.Add(new DateTimeKeyDictionaryConvention()); ConventionRegistry.Register("DateTimeKeyDictionaryConventions", conventionPack, t => true); // 初始化MongoClient(必须在约定注册之后执行) var client = new MongoClient("your_connection_string");
3. 解决System.InvalidOperationException: ValueFactory attempted to access the value property of this instance错误
这个错误通常是序列化器初始化逻辑冲突导致的,解决要点:
- 确保先注册约定,再初始化MongoClient、数据库或集合,避免序列化器未完成注册就被调用
- 序列化器的
CreateInstance方法直接返回新的Dictionary实例,不要使用可能触发递归初始化的工厂逻辑 - 若使用依赖注入,保证序列化器相关代码的注册时机早于MongoDB服务的初始化
验证示例
创建测试对象并验证序列化/反序列化:
public class TestModel { public ObjectId Id { get; set; } public Dictionary<DateTime, string> DateStringDict { get; set; } public Dictionary<DateTime, int> DateIntDict { get; set; } } // 插入测试数据 var database = client.GetDatabase("test_db"); var collection = database.GetCollection<TestModel>("test_collection"); var testData = new TestModel { DateStringDict = new() { { DateTime.Now, "test1" }, { DateTime.Now.AddHours(1), "test2" } }, DateIntDict = new() { { DateTime.Now, 100 }, { DateTime.Now.AddHours(2), 200 } } }; collection.InsertOne(testData); // 查询验证 var savedData = collection.Find(x => x.Id == testData.Id).FirstOrDefault(); // 检查savedData中的字典是否与插入数据一致
内容的提问来源于stack exchange,提问作者Brecht Vanden Houwe
相关产品推荐
相关产品推荐

