MongoDb C#驱动程序 - 将List<枚举>序列化为string[]

5

我有一个类,其中有一个类型为List<SomeEnum>的属性。就像这样:

public enum MyEnum
{
  A,
  B
}

public class MyClass
{
    public string Id { get; set; }
    public List<MyEnum> Values { get; set; }
}

我已经按照以下方式使用了EnumRepresentationConvention

ConventionRegistry.Register("EnumStringConvention", new ConventionPack { new EnumRepresentationConvention(BsonType.String) }, t => true);

然而,Values属性被序列化为整数数组(简单枚举属性正确处理为整数)。在列表序列化的上下文中似乎未使用惯例。

我该如何强制序列化程序编写字符串而不是整数?


2
我刚刚在 https://jira.mongodb.org/browse/CSHARP-2096 上为该主题创建了一个问题,并且还创建了相应的 PR,以提供所需的修复/改进:https://github.com/mongodb/mongo-csharp-driver/pull/305 - dnickless
你真棒!谢谢。 - Alberto Chiesa
非常感谢您,让我们看看它是否会被合并。 - dnickless
3个回答

7

可以通过在 MyClass 的属性 Values 上添加数据注释 [BsonRepresentation(BsonType.String)],而不是调用 ConventionRegistry.Register() 方法来实现目的。

public class MyClass
{
    public string Id { get; set; }

    [BsonRepresentation(BsonType.String)] 
    public List<MyEnum> Values { get; set; }
}

在更改之后,collection.InsertOne(obj); 将保存以下内容:

{
    "_id" : "1",
    "Values" : [ 
        "A", 
        "B"
    ]
}

1
它运行得非常完美。感谢这个解决方法,期待@dnikless的PR进入生产环境。 - Alberto Chiesa
1
@Daniel 如何通过类映射使用它? - rebornx

3

很遗憾,这个PR已被关闭,不会进行修复。 https://github.com/mongodb/mongo-csharp-driver/pull/305#issuecomment-731475503

虽然给出了好的理由,但我认为人们希望做这样的事情,所以,根据你跳过障碍的意愿不同,你可以尝试(.NET 5):

using System;
using System.Collections.Generic;
using System.Reflection;

using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;

public sealed class EnumWrapper<TEnum>
    where TEnum : struct, Enum
{
    [BsonConstructor]
    public EnumWrapper(TEnum value) => this.Value = value;

    public TEnum Value { get; }

    public static readonly IBsonSerializer<EnumWrapper<TEnum>> Serializer = new BsonSerializerImpl();

    public static implicit operator TEnum(EnumWrapper<TEnum> wrapper) => wrapper.Value;

    public static implicit operator EnumWrapper<TEnum>(TEnum value) => new(value);

    public override bool Equals(object obj) =>
        obj is EnumWrapper<TEnum> wrapper
        && EqualityComparer<TEnum>.Default.Equals(this.Value, wrapper.Value);

    public override int GetHashCode() => HashCode.Combine(this.Value);

    public override string ToString() => this.Value.ToString();

    private class BsonSerializerImpl : IBsonSerializer<EnumWrapper<TEnum>>
    {
        public Type ValueType => typeof(EnumWrapper<TEnum>);

        public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, EnumWrapper<TEnum> value) =>
            context.Writer.WriteString(((TEnum)value).ToString());

        public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value) =>
            this.Serialize(context, args, (EnumWrapper<TEnum>)value);

        object IBsonSerializer.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) =>
            this.Deserialize(context, args);

        public EnumWrapper<TEnum> Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) =>
            Enum.Parse<TEnum>(context.Reader.ReadString());
    }
}

public class EnumWrapperBsonSerializationProvider : IBsonSerializationProvider
{
    public IBsonSerializer GetSerializer(Type type)
    {
        if (!type.IsGenericType)
        {
            return null;
        }

        var typeDefinition = type.GetGenericTypeDefinition();
        if (typeDefinition != typeof(EnumWrapper<>))
        {
            return null;
        }

        var field = type.GetField(nameof(EnumWrapper<Hack>.Serializer), BindingFlags.Public | BindingFlags.Static);
        return (IBsonSerializer)field.GetValue(null);
    }

    private enum Hack { }
}

现在你可以在大多数原本需要使用 TEnum 的地方使用 EnumWrapper<TEnum>,它会按照你的期望工作。如果不注册序列化提供程序,它将会序列化为嵌套对象,因此在进行任何操作之前,你应该调用以下内容:

BsonSerializer.RegisterSerializationProvider(new EnumWrapperBsonSerializationProvider());

0
这段代码解决了将枚举集合序列化为字符串数组的问题,看起来很简单,但花了一些时间才弄明白:
调用
BsonSerializer.RegisterSerializationProvider(new EnumProvider());

在任何其他代码BSON代码之前,EnumProvider是什么?
class EnumProvider : IBsonSerializationProvider
{
    public static IBsonSerializer CreateClass<T>() where T : struct, Enum => new EnumSerializer<T>(BsonType.String);

    private static readonly MethodInfo s_mi = typeof(EnumProvider).GetMethod(nameof(CreateClass));
    public IBsonSerializer GetSerializer(Type type)
        => type.IsEnum ? (IBsonSerializer)s_mi.MakeGenericMethod(type).Invoke(null, null) : null;
}

不用担心在这里使用反射会影响性能,这段代码每个枚举类型只会被调用一次。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接