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

MongoDB BsonSerializer反序列化时BaseEvent属性值为默认值问题

问题:MongoDB事件存储中子类事件属性反序列化失败

使用MongoDb.Driver/2.19.1与C# .NET 7实现MongoDB事件存储时,StreamRecord类能正常反序列化,但其中BaseEvent子类(如PersonCreatedEvent)的属性始终为默认值,数据库中明明存储了对应数据。

相关代码

StreamRecord类

public sealed class StreamRecord
{
    [BsonId]
    [BsonRepresentation(BsonType.ObjectId)]
    public string Id { get; init; }
    public string StreamName { get; init; }
    public long Version { get; init; }
    public DateTime TimeStamp { get; init; }
    public string EventType { get; init; }
    public BaseEvent EventData { get; init; }

    public StreamRecord(string streamName, BaseEvent @event, DateTime timeStamp)
    {
        Version = @event.Version; // Versions of record in stream and Event should match.
        StreamName = streamName;
        EventType = @event.EventType;
        EventData = @event;
        TimeStamp = timeStamp;
    }
}

BaseEvent类

public abstract class BaseEvent : Message, ITrackedMessage, INotification
{
    protected BaseEvent()
    {
        // ReSharper disable once UseNameOfInsteadOfTypeOf
        EventType = GetType().Name;
        MessageType = MessageType.Event;
        TimeStamp = DateTime.Now;
    }

    /// <summary>
    /// Gets or initializes Version number of an event
    /// </summary>
    public long Version { get; set; }

    /// <summary>
    /// Gets type name of an event
    /// </summary>
    public string EventType { get; init; }

    public string? ApplicationId { get; init; }
    public string? CorrelationId { get; init; }
    public string? UserId { get; init; }
    public DateTime TimeStamp { get; init; }
}

PersonCreatedEvent类

public class PersonCreatedEvent : BaseEvent
{
    public PersonCreatedEvent(string name, int age) : base()
    {
        Name = name;
        Age = age;
    }
    public string Name { get; }
    public int Age { get; }
}

初始化代码

public static IServiceCollection AddEventStore(this IServiceCollection services,
        Func<MongoDbEventRepositoryOptions> mongoDbOptions, params Assembly[] eventsHostAssemblies)
    {
        var options = mongoDbOptions();
        
        InitializeBsonClassMaps(eventsHostAssemblies);

        services.AddScoped<IMongoDatabase>(sp =>
                new MongoClient(options.ConnectionString).GetDatabase(options.DatabaseName))
            .AddSingleton(options)
            .AddScoped<IEventRepository, MongoDbEventRepository>()
            .AddEventStore();
        
        return services;
    }

 private static void InitializeBsonClassMaps(IEnumerable<Assembly> eventsHostAssemblies)
    {
        var eventTypes = eventsHostAssemblies
            .SelectMany(a => a.GetTypes())
            .Where(x =>
                x.Name.EndsWith("Event", StringComparison.InvariantCultureIgnoreCase)
                && x.BaseType == typeof(BaseEvent)
                && x.IsClass)
            .ToList();

        BsonClassMap.RegisterClassMap<BaseEvent>(ce =>
        {
            ce.AutoMap();
            ce.SetIsRootClass(true);
            //ce.SetIgnoreExtraElements(true);
        });
        
        foreach (var eventClassMap in eventTypes.Select(eventType => new BsonClassMap(eventType)))
        {
            //eventClassMap.SetIgnoreExtraElements(true);
            BsonClassMap.RegisterClassMap(eventClassMap);
        }
    }

MongoDB存储的Bson文档

{ "_id" : ObjectId("644a86173f5ac693ebd6813b"), "StreamName" : "PersonAggregate[0]", "Version" : NumberLong(0), "TimeStamp" : ISODate("2023-04-27T14:26:31.294+0000"), "EventType" : "PersonCreatedEvent", "EventData" : { "_t" : [ "EventType", "PersonCreatedEvent" ], "MessageId" : LUUID("52B9C7B4-8FFC-7E45-88A2-585B44B4443C"), "MessageType" : NumberInt(2), "Version" : NumberLong(0), "EventType" : "PersonCreatedEvent", "ApplicationId" : null, "CorrelationId" : null, "UserId" : null, "Name" : "Jondi", "Age" : NumberInt(50) } }

解决方案

1. 修复Bson类映射的多态配置问题

当前类映射初始化未正确处理子类属性映射与多态判别符配置,修改InitializeBsonClassMaps方法:

private static void InitializeBsonClassMaps(IEnumerable<Assembly> eventsHostAssemblies)
{
    var eventTypes = eventsHostAssemblies
        .SelectMany(a => a.GetTypes())
        .Where(x =>
            x.Name.EndsWith("Event", StringComparison.InvariantCultureIgnoreCase)
            && typeof(BaseEvent).IsAssignableFrom(x)
            && x.IsClass && !x.IsAbstract)
        .ToList();

    // 配置基类映射,指定多态判别符字段并注册子类
    BsonClassMap.RegisterClassMap<BaseEvent>(cm =>
    {
        cm.AutoMap();
        cm.SetIsRootClass(true);
        cm.SetDiscriminator("_t");
        cm.SetDiscriminatorIsRequired(true);
        
        // 注册所有子类
        foreach (var type in eventTypes)
        {
            cm.AddKnownType(type);
        }
    });

    // 为每个子类自动映射属性
    foreach (var eventType in eventTypes)
    {
        BsonClassMap.LookupClassMap(eventType);
    }
}

2. 标记子类构造函数以支持只读属性反序列化

PersonCreatedEvent的Name和Age是只读属性,MongoDB驱动默认无法反序列化,添加[BsonConstructor]特性标记构造函数:

public class PersonCreatedEvent : BaseEvent
{
    [BsonConstructor]
    public PersonCreatedEvent(string name, int age) : base()
    {
        Name = name;
        Age = age;
    }
    public string Name { get; }
    public int Age { get; }
}

3. 确保映射初始化时机正确

确认InitializeBsonClassMaps在创建MongoClient之前执行,当前AddEventStore方法的顺序已经符合要求,避免后续重复注册类映射。


内容的提问来源于stack exchange,提问作者ramaz bluashvili

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 17:59:54