我有两个 .Net (C#) 应用程序通过 Mongo 传递“消息”。一个是Writer,另一个是Reader。 _t 类型鉴别器导致我出现反序列化问题。
这是他们用来通信的公共类的简化版本:
public class MyMessage {
public long Id {get; set;}
public string MessageText {get; set;}
public dynamic OriginalObject{get; set;}
}
Writer应用程序实际上是通过映射Reader应用程序未知的其他类型的值来创建MyMessage的实例。它映射出已知的公共元素,然后将整个原始对象分配给动态 OriginalObject 属性。
public MyMessage CreateMessage(SomeOtherType originalMsg) {
return new MyMessage {
MessageText = originalMsg.SomeField,
OriginalObject = originalMsg
};
}
当 .Net Mongo 驱动程序序列化 MyMessage 时,它将 _t 类型鉴别器添加到 OriginalObject 子文档中。
但是,Reader 应用程序并未引用 Writer 所引用的所有类型。当 Reader 应用程序尝试反序列化 MyMessage 时,只要
OriginalObject是它不具有的类型,它就会抛出
Unknown discriminator value
错误。
我读到,只要实际类型与名义类型不匹配,就会添加 _t 类型鉴别器。这在真正的多态、强类型场景中是有意义的。但现在 C# 有了动态类型,我想使用它。
此外,如果 MyMessage.OriginalMessage 的实例是纯匿名类型,则它可以正常工作。没有写入 _t 类型鉴别器。 Reader 应用程序愉快地将其反序列化为动态(expando)对象,并且一切正常。只有当 OriginalMessage 的实例是某种强类型时,我才会遇到此问题。
如何告诉 Mongo 驱动程序不要为动态类型添加 _t 类型鉴别符? 我也对其他解决方案/解决方法感兴趣,但简单地排除 _t 将是我的首选方法。
.Net框架4.6.1,MongoDB.Driver 2.2.3
更多示例代码:
public void SaveMessages()
{
var message1 = new MyMessage {
Id=1,
MessageText="Anonymous Message",
OriginalMessage=new{ Field1 = "f1", Field2 = "f2" }
};
_collection.InsertOne(message1); // no _t since OriginalMessage is purely anonymous
var message2 = new MyMessage {
Id=2,
MessageText="Strong Message",
OriginalMessage=new SomeOtherType{Prop1="p1", Prop2="p2"}
};
_collection.InsertOne(message2); // has _t since SomeOtherType is not "dynamic"
}
您可以使用
Project
在获取时排除某个项目,如下所示:
return await this.GetCollection()
.Find(filter: new BsonDocument("_id", handle))
.Project(Builders<BsonDocument>.Projection.Exclude("_t"))
.FirstAsync();
聚会有点晚了,但这里有一个禁止写入鉴别器的约定:
internal class NoDiscriminatorConvention : ConventionBase, IClassMapConvention
{
public void Apply(BsonClassMap classMap)
{
classMap.SetDiscriminatorIsRequired(false);
}
}
您可以使用以下方法在应用程序启动时注册它:
ConventionRegistry.Register("No discriminators for message types",
new ConventionPack { new NoDiscriminatorConvention() },
type => typeof(MyMessage).IsAssignableFrom(type));
如果您已经映射了
MyMessage
类,例如要使用 Id
属性作为 MongoDB 的 _id
元素,您可以在此处执行此操作:
BsonClassMap.RegisterClassMap<MyMessage>(cm => {
cm.MapIdMember(m => m.Id).SetSerializer(new StringSerializer(BsonType.ObjectId));
cm.AutoMap();
cm.SetDiscriminatorIsRequired(false); });
使用 mongodriver 2.10 时,mnibic 的解决方案对我不起作用。
但是我能够使用 UndiscriminatorActualTypeSerializer 来防止添加 _t 和 _v:
public class UndiscriminatedActualTypeConvention : ConventionBase, IClassMapConvention
{
public void Apply(BsonClassMap cm)
{
Type type = cm.ClassType;
if (type.IsClass
&& type != typeof(string)
&& type != typeof(object)
&& !type.IsAbstract)
{
foreach (var memberMap in cm.DeclaredMemberMaps)
{
var genericBase = typeof(UndiscriminatedActualTypeSerializer<>);
var genericType = genericBase.MakeGenericType(new[] { memberMap.MemberType });
var serializer = genericType
.GetProperty("Instance", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static)
.GetValue(null, null) as IBsonSerializer;
cm.MapMember(memberMap.MemberInfo).SetSerializer(serializer);
}
}
}
}
程序开始时的用法:
ConventionRegistry.Register("No discriminators",
new ConventionPack { new UndiscriminatedActualTypeConvention() },
type => true);
https://stackoverflow.com/a/63686596/1536933(由Kevin Smith)有一个对我有用的答案。对于首先发现这个问题的人来说,其要点是:
public class NullDiscriminatorConvention : IDiscriminatorConvention
{
public static NullDiscriminatorConvention Instance { get; }
= new NullDiscriminatorConvention();
public Type GetActualType(IBsonReader bsonReader, Type nominalType)
=> nominalType;
public BsonValue GetDiscriminator(Type nominalType, Type actualType)
=> null;
public string ElementName { get; } = null;
}
BsonSerializer.RegisterDiscriminatorConvention(YourType, NullDiscriminatorConvention.Instance);