如何在 Bson 数组序列化器上实现 TryGetItemSerializationInfo?
How do I implement TryGetItemSerializationInfo on a Bson Array Serializer?
我们使用 MongoDb 来存储产品属性,并创建了一个自定义数组序列化程序来获取字典并以自定义格式对其进行序列化。该格式采用每个字典项的键和值,并将其存储为用冒号分隔的单个数组项。
示例产品 class
public class Product
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string Name { get; set; }
[BsonSerializer(typeof(ProductAttributeSerializer))]
public Dictionary<string, string> Attributes { get; set; } = new Dictionary<string, string>();
}
具有自定义数组格式的示例数据
{
"_id" : ObjectId("56d94ee01992d148b4c10d59"),
"Name" : "My Product",
"Attributes" : [
"Colour:Blue",
"Gender:Mens"
]
}
这是我们的序列化程序
[BsonSerializer(typeof(ProductAttributeSerializer))]
public class ProductAttributeSerializer : IBsonSerializer, IBsonArraySerializer
{
public Type ValueType { get { return typeof(Dictionary<string, string>); } }
public object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var type = context.Reader.GetCurrentBsonType();
var attributes = new Dictionary<string, string>();
switch (type)
{
case BsonType.Array:
context.Reader.ReadStartArray();
while (context.Reader.ReadBsonType() != BsonType.EndOfDocument)
{
string[] splitValues = context.Reader.ReadString().Split(':');
attributes.Add(splitValues[0], splitValues[1]);
}
context.Reader.ReadEndArray();
return attributes;
default:
throw new NotImplementedException($"No implementation to deserialize {type}");
}
}
public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
{
var attributes = value as Dictionary<string, string>;
if (attributes != null)
{
context.Writer.WriteStartArray();
foreach (KeyValuePair<string, string> attr in attributes)
{
context.Writer.WriteString($"{attr.Key}:{attr.Value}");
}
context.Writer.WriteEndArray();
}
}
public bool TryGetItemSerializationInfo(out BsonSerializationInfo serializationInfo)
{
string elementName = null;
var serializer = BsonSerializer.LookupSerializer(typeof(string));
var nominalType = typeof(string);
serializationInfo = new BsonSerializationInfo(elementName, serializer, nominalType);
return true;
}
}
项目的所有序列化都有效,但是当尝试通过驱动程序查询数据并尝试执行与此等效的操作时 db.Product.find({ Attributes : { $in : [ "Colour:Blue" ] } })
我收到错误。
最初的错误表明序列化程序需要是 IBsonArraySerializer
,现在我需要实现 TryGetItemSerializationInfo
方法。
根据上面的例子,这个方法应该做什么并设置为它的输出?
我曾尝试查找并使用字符串序列化程序,但它给出了一个错误 "Unable to cast object of type 'System.Char' to type 'System.String'"。尝试使用 char 序列化程序不会出错,但不会 return 任何结果。在日志中查找 MongoDb 似乎表明搜索已逐字符转换为整数,然后进行搜索。
过滤语句的代码也在下面
Database.GetCollection<Product>("Product").Find(Builders<Product>.Filter.AnyIn("Attributes", "Colour:Blue")).ToListAsync()
首先,序列化程序做得很好。我不需要更改其中的任何代码就可以正常工作。
我发现了问题。当您使用 AnyIn 方法时,您传递 "Colour:Blue" 的参数需要一个可枚举的。由于 .NET 字符串实现了 IEnumerable,这就是您看到奇怪的查询转换的原因。将其更改为 new [] { "Colour:Blue" } 将解决您的问题。另一种方法是,当您只搜索单个键值对时,您可以使用 AnyEq 代替。
最后,您可以从 ProductAttributeSerializer class 中删除 BsonSerializer 属性。此属性用于指示将哪个序列化程序用于 class 或 属性,而不是用于识别序列化程序本身。
我们使用 MongoDb 来存储产品属性,并创建了一个自定义数组序列化程序来获取字典并以自定义格式对其进行序列化。该格式采用每个字典项的键和值,并将其存储为用冒号分隔的单个数组项。
示例产品 class
public class Product
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string Name { get; set; }
[BsonSerializer(typeof(ProductAttributeSerializer))]
public Dictionary<string, string> Attributes { get; set; } = new Dictionary<string, string>();
}
具有自定义数组格式的示例数据
{
"_id" : ObjectId("56d94ee01992d148b4c10d59"),
"Name" : "My Product",
"Attributes" : [
"Colour:Blue",
"Gender:Mens"
]
}
这是我们的序列化程序
[BsonSerializer(typeof(ProductAttributeSerializer))]
public class ProductAttributeSerializer : IBsonSerializer, IBsonArraySerializer
{
public Type ValueType { get { return typeof(Dictionary<string, string>); } }
public object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var type = context.Reader.GetCurrentBsonType();
var attributes = new Dictionary<string, string>();
switch (type)
{
case BsonType.Array:
context.Reader.ReadStartArray();
while (context.Reader.ReadBsonType() != BsonType.EndOfDocument)
{
string[] splitValues = context.Reader.ReadString().Split(':');
attributes.Add(splitValues[0], splitValues[1]);
}
context.Reader.ReadEndArray();
return attributes;
default:
throw new NotImplementedException($"No implementation to deserialize {type}");
}
}
public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
{
var attributes = value as Dictionary<string, string>;
if (attributes != null)
{
context.Writer.WriteStartArray();
foreach (KeyValuePair<string, string> attr in attributes)
{
context.Writer.WriteString($"{attr.Key}:{attr.Value}");
}
context.Writer.WriteEndArray();
}
}
public bool TryGetItemSerializationInfo(out BsonSerializationInfo serializationInfo)
{
string elementName = null;
var serializer = BsonSerializer.LookupSerializer(typeof(string));
var nominalType = typeof(string);
serializationInfo = new BsonSerializationInfo(elementName, serializer, nominalType);
return true;
}
}
项目的所有序列化都有效,但是当尝试通过驱动程序查询数据并尝试执行与此等效的操作时 db.Product.find({ Attributes : { $in : [ "Colour:Blue" ] } })
我收到错误。
最初的错误表明序列化程序需要是 IBsonArraySerializer
,现在我需要实现 TryGetItemSerializationInfo
方法。
根据上面的例子,这个方法应该做什么并设置为它的输出?
我曾尝试查找并使用字符串序列化程序,但它给出了一个错误 "Unable to cast object of type 'System.Char' to type 'System.String'"。尝试使用 char 序列化程序不会出错,但不会 return 任何结果。在日志中查找 MongoDb 似乎表明搜索已逐字符转换为整数,然后进行搜索。
过滤语句的代码也在下面
Database.GetCollection<Product>("Product").Find(Builders<Product>.Filter.AnyIn("Attributes", "Colour:Blue")).ToListAsync()
首先,序列化程序做得很好。我不需要更改其中的任何代码就可以正常工作。
我发现了问题。当您使用 AnyIn 方法时,您传递 "Colour:Blue" 的参数需要一个可枚举的。由于 .NET 字符串实现了 IEnumerable,这就是您看到奇怪的查询转换的原因。将其更改为 new [] { "Colour:Blue" } 将解决您的问题。另一种方法是,当您只搜索单个键值对时,您可以使用 AnyEq 代替。
最后,您可以从 ProductAttributeSerializer class 中删除 BsonSerializer 属性。此属性用于指示将哪个序列化程序用于 class 或 属性,而不是用于识别序列化程序本身。