将 Dictionary<DateTime,T> 序列化为 BSON 时出现 BsonSerializationException
BsonSerializationException when serializing a Dictionary<DateTime,T> to BSON
我最近搬到了 new MongoDB C# driver v2.0 from the deprecated v1.9。
现在,当我将具有字典的 class 序列化时,有时 运行 会变成以下 BsonSerializationException
:
MongoDB.Bson.BsonSerializationException: When using DictionaryRepresentation.Document key values must serialize as strings.
这是一个最小的复制:
class Hamster
{
public ObjectId Id { get; private set; }
public Dictionary<DateTime,int> Dictionary { get; private set; }
public Hamster()
{
Id = ObjectId.GenerateNewId();
Dictionary = new Dictionary<DateTime, int>();
Dictionary[DateTime.UtcNow] = 0;
}
}
static void Main()
{
Console.WriteLine(new Hamster().ToJson());
}
问题是新驱动程序默认将字典序列化为文档。
MongoDB C# 驱动程序有 3 种序列化字典的方法:Document
、ArrayOfArrays
和 ArrayOfDocuments
(more on that in the docs)。当它序列化为文档时,字典键是 BSON 元素的名称,它有一些限制(例如,如错误提示,它们必须序列化为字符串)。
在这种情况下,字典的键是 DateTime
,它没有序列化为字符串,而是 Date
,因此我们需要选择另一个 DictionaryRepresentation
。
要更改此特定 属性 的序列化,我们可以使用具有不同 DictionaryRepresentation
:
的 BsonDictionaryOptions
属性
[BsonDictionaryOptions(DictionaryRepresentation.ArrayOfArrays)]
public Dictionary<DateTime, int> Dictionary { get; private set; }
但是,我们需要对每个有问题的成员单独执行此操作。要将此 DictionaryRepresentation
应用于所有相关成员,我们可以实施一个新约定:
class DictionaryRepresentationConvention : ConventionBase, IMemberMapConvention
{
private readonly DictionaryRepresentation _dictionaryRepresentation;
public DictionaryRepresentationConvention(DictionaryRepresentation dictionaryRepresentation)
{
_dictionaryRepresentation = dictionaryRepresentation;
}
public void Apply(BsonMemberMap memberMap)
{
memberMap.SetSerializer(ConfigureSerializer(memberMap.GetSerializer()));
}
private IBsonSerializer ConfigureSerializer(IBsonSerializer serializer)
{
var dictionaryRepresentationConfigurable = serializer as IDictionaryRepresentationConfigurable;
if (dictionaryRepresentationConfigurable != null)
{
serializer = dictionaryRepresentationConfigurable.WithDictionaryRepresentation(_dictionaryRepresentation);
}
var childSerializerConfigurable = serializer as IChildSerializerConfigurable;
return childSerializerConfigurable == null
? serializer
: childSerializerConfigurable.WithChildSerializer(ConfigureSerializer(childSerializerConfigurable.ChildSerializer));
}
}
其中我们注册如下:
ConventionRegistry.Register(
"DictionaryRepresentationConvention",
new ConventionPack {new DictionaryRepresentationConvention(DictionaryRepresentation.ArrayOfArrays)},
_ => true);
上面的答案很好,但不幸的是在某些递归对象层次结构上触发了 WhosebugException - 这是一个略有改进的最新版本。
public class DictionaryRepresentationConvention : ConventionBase, IMemberMapConvention
{
private readonly DictionaryRepresentation _dictionaryRepresentation;
public DictionaryRepresentationConvention(DictionaryRepresentation dictionaryRepresentation = DictionaryRepresentation.ArrayOfDocuments)
{
// see http://mongodb.github.io/mongo-csharp-driver/2.2/reference/bson/mapping/#dictionary-serialization-options
_dictionaryRepresentation = dictionaryRepresentation;
}
public void Apply(BsonMemberMap memberMap)
{
memberMap.SetSerializer(ConfigureSerializer(memberMap.GetSerializer(),Array.Empty<IBsonSerializer>()));
}
private IBsonSerializer ConfigureSerializer(IBsonSerializer serializer, IBsonSerializer[] stack)
{
if (serializer is IDictionaryRepresentationConfigurable dictionaryRepresentationConfigurable)
{
serializer = dictionaryRepresentationConfigurable.WithDictionaryRepresentation(_dictionaryRepresentation);
}
if (serializer is IChildSerializerConfigurable childSerializerConfigurable)
{
if (!stack.Contains(childSerializerConfigurable.ChildSerializer))
{
var newStack = stack.Union(new[] { serializer }).ToArray();
var childConfigured = ConfigureSerializer(childSerializerConfigurable.ChildSerializer, newStack);
return childSerializerConfigurable.WithChildSerializer(childConfigured);
}
}
return serializer;
}
如果你像我一样只是想将它应用于 class 中的单个字段,我是这样实现的(感谢其他答案):
BsonClassMap.RegisterClassMap<TestClass>(cm =>
{
cm.AutoMap();
var memberMap = cm.GetMemberMap(x => x.DictionaryField);
var serializer = memberMap.GetSerializer();
if (serializer is IDictionaryRepresentationConfigurable dictionaryRepresentationSerializer)
serializer = dictionaryRepresentationSerializer.WithDictionaryRepresentation(DictionaryRepresentation.ArrayOfDocuments);
memberMap.SetSerializer(serializer);
});
或作为扩展方法:
BsonClassMap.RegisterClassMap<TestClass>(cm =>
{
cm.AutoMap();
cm.SetDictionaryRepresentation(x => x.DictionaryField, DictionaryRepresentation.ArrayOfDocuments);
});
public static class MapHelpers
{
public static BsonClassMap<T> SetDictionaryRepresentation<T, TMember>(this BsonClassMap<T> classMap, Expression<Func<T,TMember>> memberLambda, DictionaryRepresentation representation)
{
var memberMap = classMap.GetMemberMap(memberLambda);
var serializer = memberMap.GetSerializer();
if (serializer is IDictionaryRepresentationConfigurable dictionaryRepresentationSerializer)
serializer = dictionaryRepresentationSerializer.WithDictionaryRepresentation(representation);
memberMap.SetSerializer(serializer);
return classMap;
}
}
我最近搬到了 new MongoDB C# driver v2.0 from the deprecated v1.9。
现在,当我将具有字典的 class 序列化时,有时 运行 会变成以下 BsonSerializationException
:
MongoDB.Bson.BsonSerializationException: When using DictionaryRepresentation.Document key values must serialize as strings.
这是一个最小的复制:
class Hamster
{
public ObjectId Id { get; private set; }
public Dictionary<DateTime,int> Dictionary { get; private set; }
public Hamster()
{
Id = ObjectId.GenerateNewId();
Dictionary = new Dictionary<DateTime, int>();
Dictionary[DateTime.UtcNow] = 0;
}
}
static void Main()
{
Console.WriteLine(new Hamster().ToJson());
}
问题是新驱动程序默认将字典序列化为文档。
MongoDB C# 驱动程序有 3 种序列化字典的方法:Document
、ArrayOfArrays
和 ArrayOfDocuments
(more on that in the docs)。当它序列化为文档时,字典键是 BSON 元素的名称,它有一些限制(例如,如错误提示,它们必须序列化为字符串)。
在这种情况下,字典的键是 DateTime
,它没有序列化为字符串,而是 Date
,因此我们需要选择另一个 DictionaryRepresentation
。
要更改此特定 属性 的序列化,我们可以使用具有不同 DictionaryRepresentation
:
BsonDictionaryOptions
属性
[BsonDictionaryOptions(DictionaryRepresentation.ArrayOfArrays)]
public Dictionary<DateTime, int> Dictionary { get; private set; }
但是,我们需要对每个有问题的成员单独执行此操作。要将此 DictionaryRepresentation
应用于所有相关成员,我们可以实施一个新约定:
class DictionaryRepresentationConvention : ConventionBase, IMemberMapConvention
{
private readonly DictionaryRepresentation _dictionaryRepresentation;
public DictionaryRepresentationConvention(DictionaryRepresentation dictionaryRepresentation)
{
_dictionaryRepresentation = dictionaryRepresentation;
}
public void Apply(BsonMemberMap memberMap)
{
memberMap.SetSerializer(ConfigureSerializer(memberMap.GetSerializer()));
}
private IBsonSerializer ConfigureSerializer(IBsonSerializer serializer)
{
var dictionaryRepresentationConfigurable = serializer as IDictionaryRepresentationConfigurable;
if (dictionaryRepresentationConfigurable != null)
{
serializer = dictionaryRepresentationConfigurable.WithDictionaryRepresentation(_dictionaryRepresentation);
}
var childSerializerConfigurable = serializer as IChildSerializerConfigurable;
return childSerializerConfigurable == null
? serializer
: childSerializerConfigurable.WithChildSerializer(ConfigureSerializer(childSerializerConfigurable.ChildSerializer));
}
}
其中我们注册如下:
ConventionRegistry.Register(
"DictionaryRepresentationConvention",
new ConventionPack {new DictionaryRepresentationConvention(DictionaryRepresentation.ArrayOfArrays)},
_ => true);
上面的答案很好,但不幸的是在某些递归对象层次结构上触发了 WhosebugException - 这是一个略有改进的最新版本。
public class DictionaryRepresentationConvention : ConventionBase, IMemberMapConvention
{
private readonly DictionaryRepresentation _dictionaryRepresentation;
public DictionaryRepresentationConvention(DictionaryRepresentation dictionaryRepresentation = DictionaryRepresentation.ArrayOfDocuments)
{
// see http://mongodb.github.io/mongo-csharp-driver/2.2/reference/bson/mapping/#dictionary-serialization-options
_dictionaryRepresentation = dictionaryRepresentation;
}
public void Apply(BsonMemberMap memberMap)
{
memberMap.SetSerializer(ConfigureSerializer(memberMap.GetSerializer(),Array.Empty<IBsonSerializer>()));
}
private IBsonSerializer ConfigureSerializer(IBsonSerializer serializer, IBsonSerializer[] stack)
{
if (serializer is IDictionaryRepresentationConfigurable dictionaryRepresentationConfigurable)
{
serializer = dictionaryRepresentationConfigurable.WithDictionaryRepresentation(_dictionaryRepresentation);
}
if (serializer is IChildSerializerConfigurable childSerializerConfigurable)
{
if (!stack.Contains(childSerializerConfigurable.ChildSerializer))
{
var newStack = stack.Union(new[] { serializer }).ToArray();
var childConfigured = ConfigureSerializer(childSerializerConfigurable.ChildSerializer, newStack);
return childSerializerConfigurable.WithChildSerializer(childConfigured);
}
}
return serializer;
}
如果你像我一样只是想将它应用于 class 中的单个字段,我是这样实现的(感谢其他答案):
BsonClassMap.RegisterClassMap<TestClass>(cm =>
{
cm.AutoMap();
var memberMap = cm.GetMemberMap(x => x.DictionaryField);
var serializer = memberMap.GetSerializer();
if (serializer is IDictionaryRepresentationConfigurable dictionaryRepresentationSerializer)
serializer = dictionaryRepresentationSerializer.WithDictionaryRepresentation(DictionaryRepresentation.ArrayOfDocuments);
memberMap.SetSerializer(serializer);
});
或作为扩展方法:
BsonClassMap.RegisterClassMap<TestClass>(cm =>
{
cm.AutoMap();
cm.SetDictionaryRepresentation(x => x.DictionaryField, DictionaryRepresentation.ArrayOfDocuments);
});
public static class MapHelpers
{
public static BsonClassMap<T> SetDictionaryRepresentation<T, TMember>(this BsonClassMap<T> classMap, Expression<Func<T,TMember>> memberLambda, DictionaryRepresentation representation)
{
var memberMap = classMap.GetMemberMap(memberLambda);
var serializer = memberMap.GetSerializer();
if (serializer is IDictionaryRepresentationConfigurable dictionaryRepresentationSerializer)
serializer = dictionaryRepresentationSerializer.WithDictionaryRepresentation(representation);
memberMap.SetSerializer(serializer);
return classMap;
}
}