You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 03:36:25