在Mongodb驱动程序内部的序列化筛选器

huangapple go评论79阅读模式
英文:

Serialization inside Mongodb driver filter

问题

我试图在`Type`字段上做一个选择,`CloudFileTypes`是一个枚举类型。如果你尝试使用linq方法或mongodb驱动程序筛选来进行选择,它会将`Type`视为一个数字,无法找到所需的文件,在数据库中`Type`字段的值是字符串。如果我选择了某个特定的文件并访问类型字段,它会返回枚举成员,就像需要的那样。
我提前道歉,如果我误解了。

代码中的问题部分:

public CloudFile GetRoot(User user)
{
var builder = Builders.Filter;
var query = builder.Eq(e => e.Type, CloudFileTypes.Root);
var root = _fileCollection.Find(query).First();

return root.First();

}


`CloudFile` 模型:

public class CloudFile
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
[BsonElement("name")] public string Name { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
[BsonElement("type")] public CloudFileTypes Type { get; set; }
[BsonElement("size")] public int Size { get; set; }
[BsonElement("path")] public string Path { get; set; }
[BsonRepresentation(BsonType.ObjectId)]
[BsonElement("user")] public string User { get; set; }
[BsonRepresentation(BsonType.ObjectId)]
[BsonElement("parent")] public string Parent { get; set; }
[BsonRepresentation(BsonType.ObjectId)]
[BsonElement("childs")] public string[] Childs { get; set; }
}
public enum CloudFileTypes
{
[EnumMember(Value = "root")] Root,
[EnumMember(Value = "folder")] Folder,
[EnumMember(Value = "file")] File,
}


[根文件的示例。](https://i.stack.imgur.com/3rOcl.png)


我该如何解决这个问题。尝试过谷歌搜索,但没有成功。

**编辑:** *我创建了一个Json转换器,用于在将对象发送到客户端时序列化`enum`,使用了Yong Shun的扩展:*

public class EnumStringConverter : JsonConverter where TEnum: struct, Enum
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(CloudFileTypes);
}
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value is not TEnum @enum)
return;

    if (!Attribute.IsDefined(@enum.GetType().GetMember(@enum.ToString()).FirstOrDefault(), typeof(EnumMemberAttribute)))
        writer.WriteValue(@enum.ToString());
    writer.WriteValue(EnumExtensions.GetEnumMemberValue(@enum));
}
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
    JObject jo = JObject.Load(reader);
    var str = jo.ToString();
    var obj = EnumExtensions.EnumMemberValueToEnum<CloudFileTypes>(str);
    return obj;
}

}


<details>
<summary>英文:</summary>

I&#39;m trying to make a selection on the `Type` field, `CloudFileTypes` is an `enum`. If you try to make a selection using linq methods or mongodb driver filter, then it perceives `Type` as a number and cannot find the desired file, and in the database in the `Type` field I have `string` values. And if I select some specific file and access the type field, then it will return the enumeration member, as needed.
I apologize in advance if I misunderstood.

Problem part of code:

public CloudFile GetRoot(User user)
{
var builder = Builders<CloudFile>.Filter;
var query = builder.Eq(e => e.Type, CloudFileTypes.Root);
var root = _fileCollection.Find(query).First();

return root.First();

}


`CloudFile` model:

public class CloudFile
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
[BsonElement("name")] public string Name { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
[BsonElement("type")] public CloudFileTypes Type { get; set; }
[BsonElement("size")] public int Size { get; set; }
[BsonElement("path")] public string Path { get; set; }
[BsonRepresentation(BsonType.ObjectId)]
[BsonElement("user")] public string User { get; set; }
[BsonRepresentation(BsonType.ObjectId)]
[BsonElement("parent")] public string Parent { get; set; }
[BsonRepresentation(BsonType.ObjectId)]
[BsonElement("childs")] public string[] Childs { get; set; }
}
public enum CloudFileTypes
{
[EnumMember(Value = "root")] Root,
[EnumMember(Value = "folder")] Folder,
[EnumMember(Value = "file")] File,
}


[Example of root file.](https://i.stack.imgur.com/3rOcl.png)


How do i solve this problem. Tried googling, didn&#39;t work.



**EDIT:** *JsonConverter I created to serialize the `enum` when the object is sent to the client, using the extension from Yong Shun:*

public class EnumStringConverter<TEnum> : JsonConverter where TEnum: struct, Enum
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(CloudFileTypes);
}
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value is not TEnum @enum)
return;

        if (!Attribute.IsDefined(@enum.GetType().GetMember(@enum.ToString()).FirstOrDefault(), typeof(EnumMemberAttribute)))
            writer.WriteValue(@enum.ToString());
        writer.WriteValue(EnumExtensions.GetEnumMemberValue(@enum));
    }
    public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
    {
        JObject jo = JObject.Load(reader);
        var str = jo.ToString();
        var obj = EnumExtensions.EnumMemberValueToEnum&lt;CloudFileTypes&gt;(str);
        return obj;
    }
}


</details>


# 答案1
**得分**: 0

以下是要翻译的部分:

1. A helper/extension method that allows you to convert from `string` to `Enum` (which implements the `EnumMemberAttribute` and vice versa.

```csharp
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;

public static class EnumExtensions
{
    public static string GetEnumMemberValue&lt;TEnum&gt;(this TEnum @enum)
        where TEnum : struct, Enum
    {
        string enumMemberValue = @enum.GetType()
            .GetMember(@enum.ToString())
            .FirstOrDefault()?
            .GetCustomAttributes&lt;EnumMemberAttribute&gt;(false)
            .FirstOrDefault()?
            .Value;

        if (enumMemberValue == null)
            return @enum.ToString();
            //throw new ArgumentException($&quot;Enum {@enum.GetType().Name} with member {@enum} not apply {nameof(EnumMemberAttribute)} attribute.&quot;);

        return enumMemberValue;
    }

    public static TEnum EnumMemberValueToEnum&lt;TEnum&gt;(this string value)
        where TEnum : struct, Enum
    {
        foreach (var field in typeof(TEnum).GetFields())
        {
            if (Attribute.GetCustomAttribute(field,
                typeof(EnumMemberAttribute)) is EnumMemberAttribute attribute)
            {
                if (attribute.Value == value)
                    return (TEnum)field.GetValue(null);
            }

            if (field.Name == value)
                return (TEnum)field.GetValue(null);
        }

        throw an ArgumentException($&quot;{value} is not found.&quot;);
    }
}
  1. Implement and register a custom serializer for converting the string value (from EnumMemberAttribute value) to Enum and vice versa. Reference: @wilver's answer on Storing Enums as strings in MongoDB
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson.Serialization.Serializers;

public class EnumMemberStringSerializer&lt;TEnum&gt; : ObjectSerializer, IBsonSerializer&lt;TEnum&gt;
    where TEnum : struct, Enum
{
    public new Type ValueType =&gt; typeof(TEnum);

    public TEnum Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
    {
        return EnumExtensions.EnumMemberValueToEnum&lt;TEnum&gt;(base.Deserialize(context, args)?.ToString());
    }

    public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TEnum value)
    {
        base.Serialize(context, args, EnumExtensions.GetEnumMemberValue((TEnum)value));
    }

    public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
    {
        base.Serialize(context, args, EnumExtensions.GetEnumMemberValue((TEnum)value));
    }

    object IBsonSerializer.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
    {
        return EnumExtensions.EnumMemberValueToEnum&lt;TEnum&gt;(base.Deserialize(context, args)?.ToString());
    }
}
  1. Register the EnumMemberStringSerializer serializer (before IMongoDatabase & IMongoCollection instances are created).
BsonSerializer.RegisterSerializer(typeof(CloudFileTypes), new EnumMemberStringSerializer&lt;CloudFileTypes&gt;());
英文:

Tricky yet interesting question.

As mentioned that you are storing the enum value as string in the collection, based on your current query, MongoDB Fluent API will pass the enum value as an integer but not the string value from the EnumMemberAttribute.

Warning: This will be a long answer.

These are the steps in order to allow the MongoDB Fluent API to pass the value from the EnumMemberAttribute in the query.

  1. A helper/extension method that allows you to convert from string to Enum (which implements the EnumMemberAttribute and vice versa.
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;

public static class EnumExtensions
{
    public static string GetEnumMemberValue&lt;TEnum&gt;(this TEnum @enum)
        where TEnum : struct, Enum
    {
        string enumMemberValue = @enum.GetType()
            .GetMember(@enum.ToString())
            .FirstOrDefault()?
            .GetCustomAttributes&lt;EnumMemberAttribute&gt;(false)
            .FirstOrDefault()?
            .Value;

        if (enumMemberValue == null)
            return @enum.ToString();
            //throw new ArgumentException($&quot;Enum {@enum.GetType().Name} with member {@enum} not apply {nameof(EnumMemberAttribute)} attribute.&quot;);

        return enumMemberValue;
    }

    public static TEnum EnumMemberValueToEnum&lt;TEnum&gt;(this string value)
        where TEnum : struct, Enum
    {
        foreach (var field in typeof(TEnum).GetFields())
        {
            if (Attribute.GetCustomAttribute(field,
                typeof(EnumMemberAttribute)) is EnumMemberAttribute attribute)
            {
                if (attribute.Value == value)
                    return (TEnum)field.GetValue(null);
            }

            if (field.Name == value)
                return (TEnum)field.GetValue(null);
        }

        throw new ArgumentException($&quot;{value} is not found.&quot;);
    }
}
  1. Implement and register a custom serializer for converting the string value (from EnumMemberAttribute value) to Enum and vice versa. Reference: @wilver's answer on Storing Enums as strings in MongoDB
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson.Serialization.Serializers;

public class EnumMemberStringSerializer&lt;TEnum&gt; : ObjectSerializer, IBsonSerializer&lt;TEnum&gt;
    where TEnum : struct, Enum
{
    public new Type ValueType =&gt; typeof(TEnum);

    public TEnum Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
    {
        return EnumExtensions.EnumMemberValueToEnum&lt;TEnum&gt;(base.Deserialize(context, args)?.ToString());
    }

    public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TEnum value)
    {
        base.Serialize(context, args, EnumExtensions.GetEnumMemberValue((TEnum)value));
    }

    public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
    {
        base.Serialize(context, args, EnumExtensions.GetEnumMemberValue((TEnum)value));
    }

    object IBsonSerializer.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
    {
        return EnumExtensions.EnumMemberValueToEnum&lt;TEnum&gt;(base.Deserialize(context, args)?.ToString());
    }
}
  1. Register the EnumMemberStringSerializer serializer (before IMongoDatabase & IMongoCollection instances are created).
BsonSerializer.RegisterSerializer(typeof(CloudFileTypes), new EnumMemberStringSerializer&lt;CloudFileTypes&gt;());

huangapple
  • 本文由 发表于 2023年7月28日 02:28:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/76782517.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定