MongoDB 自定义序列化程序以避免添加 _t collection,抛出 ReadEndArray 错误?
MongoDB Custom Serializer to avoid _t being added collection, throws ReadEndArray Error?
情况:
语言:C# 使用 C# 驱动程序
我有一个包含 属性 列表的模型。该列表可以包含全部继承 BaseModelClass 的 3 个不同模型之一。为了帮助序列化这种情况,Mongo 添加了 _t 以识别实际使用的模型。对我们来说这是一个问题,因为 _t 占用了 space 的数量。我是一个低级开发人员,我要求更多 space 和 ram,他们告诉我无需额外的 space 即可解决它。所以我坐下来编写一个自定义序列化程序来处理不同的类型,而无需将 _t 写入 BSONDocument。在我开始对序列化进行单元测试之前,我认为一切都很好。我开始 "ReadEndArray can only be called when ContextType is Array, not when ContextType is Document."
非常感谢任何意见或建议。
这是我到目前为止的代码...
<----------Collection型号-------------------->
[BsonCollectionName("calls")]
[BsonIgnoreExtraElements]
public class Call
{
[BsonId]
public CallId _id { get; set; }
[BsonElement("responses")]
[BsonIgnoreIfNull]
public IList<DataRecord> Responses { get; set; }
}
<------------基础数据记录---------------->
[BsonSerializer(typeof(DataRecordSerializer))]
public abstract class DataRecord
{
[BsonElement("key")]
public string Key { get; set; }
}
<------------实际数据记录示例---------------->
[BsonSerializer(typeof(DataRecordSerializer))]
public class DataRecordInt : DataRecord
{
[BsonElement("value")]
public int Value { get; set; }
}
[BsonSerializer(typeof(DataRecordSerializer))]
public class DataRecordDateTime : DataRecord
{
[BsonElement("value")]
public DateTime? Value { get; set; }
}
<----------------触发解串器的单元测试---------------->
//Arrange
var bsonDocument = TestResources.SampleCallJson;
//Act
var result = BsonSerializer.Deserialize<Call>(bsonDocument);
//Assert
Assert.IsTrue(true);
<----------------序列化器---------------->
public class DataRecordSerializer : IBsonSerializer
{
public object Deserialize(BsonReader bsonReader, Type nominalType, IBsonSerializationOptions options)
{
//Entrance Criteria
if(nominalType != typeof(DataRecord)) throw new BsonSerializationException("Must be of base type DataRecord.");
if(bsonReader.GetCurrentBsonType() != BsonType.Document) throw new BsonSerializationException("Must be of type Document.");
bsonReader.ReadStartDocument();
var key = bsonReader.ReadString("key");
bsonReader.FindElement("value");
var bsonType = bsonReader.CurrentBsonType;
if (bsonType == BsonType.DateTime)
{
return DeserializeDataRecordDateTime(bsonReader, key);
}
return bsonType == BsonType.Int32 ? DeserializeDataRecordInt(bsonReader, key) : DeserializeDataRecordString(bsonReader, key);
}
public object Deserialize(BsonReader bsonReader, Type nominalType, Type actualType, IBsonSerializationOptions options)
{
//Entrance Criteria
if (nominalType != typeof (DataRecord)) throw new BsonSerializationException("Must be of base type DataRecord.");
if (bsonReader.GetCurrentBsonType() != BsonType.Document) throw new BsonSerializationException("Must be of type Document.");
bsonReader.ReadStartDocument(); // Starts Reading and is able to pull data fine through this and the next few lines of code.
var key = bsonReader.ReadString("key");
if (actualType == typeof(DataRecordDateTime))
{
return DeserializeDataRecordDateTime(bsonReader, key);
}
return actualType == typeof(DataRecordInt) ? DeserializeDataRecordInt(bsonReader, key) : DeserializeDataRecordString(bsonReader, key); // Once it tries to return I am getting the following Error: ReadEndArray can only be called when ContextType is Array, not when ContextType is Document.
}
public IBsonSerializationOptions GetDefaultSerializationOptions()
{
return new DocumentSerializationOptions
{
AllowDuplicateNames = false,
SerializeIdFirst = false
};
}
public void Serialize(BsonWriter bsonWriter, Type nominalType, object value, IBsonSerializationOptions options)
{
var currentType = value.GetType();
if (currentType == typeof (DataRecordInt))
{
SerializeDataRecordInt(bsonWriter, value);
return;
}
if (currentType == typeof(DataRecordDateTime))
{
SerializeDataRecordDateTime(bsonWriter, value);
return;
}
if (currentType == typeof(DataRecordString))
{
SerializeDataRecordString(bsonWriter, value);
}
}
private static object DeserializeDataRecordString(BsonReader bsonReader, string key)
{
var stringValue = bsonReader.ReadString();
var isCommentValue = false;
if (bsonReader.FindElement("iscomment"))
{
isCommentValue = bsonReader.ReadBoolean();
}
return new DataRecordString
{
Key = key,
Value = stringValue,
IsComment = isCommentValue
};
}
private static object DeserializeDataRecordInt(BsonReader bsonReader, string key)
{
var intValue = bsonReader.ReadInt32();
return new DataRecordInt
{
Key = key,
Value = intValue
};
}
private static object DeserializeDataRecordDateTime(BsonReader bsonReader, string key)
{
var dtValue = bsonReader.ReadDateTime();
var dateTimeValue = new BsonDateTime(dtValue).ToUniversalTime();
return new DataRecordDateTime
{
Key = key,
Value = dateTimeValue
};
}
private static void SerializeDataRecordString(BsonWriter bsonWriter, object value)
{
var stringRecord = (DataRecordString) value;
bsonWriter.WriteStartDocument();
var keyValue = stringRecord.Key;
bsonWriter.WriteString("key", string.IsNullOrEmpty(keyValue) ? string.Empty : keyValue);
var valueValue = stringRecord.Value;
bsonWriter.WriteString("value", string.IsNullOrEmpty(valueValue) ? string.Empty : valueValue);
bsonWriter.WriteBoolean("iscomment", stringRecord.IsComment);
bsonWriter.WriteEndDocument();
}
private static void SerializeDataRecordDateTime(BsonWriter bsonWriter, object value)
{
var dateRecord = (DataRecordDateTime) value;
var millisecondsSinceEpoch = dateRecord.Value.HasValue
? BsonUtils.ToMillisecondsSinceEpoch(new DateTime(dateRecord.Value.Value.Ticks, DateTimeKind.Utc))
: 0;
bsonWriter.WriteStartDocument();
var keyValue = dateRecord.Key;
bsonWriter.WriteString("key", string.IsNullOrEmpty(keyValue) ? string.Empty : keyValue);
if (millisecondsSinceEpoch != 0)
{
bsonWriter.WriteDateTime("value", millisecondsSinceEpoch);
}
else
{
bsonWriter.WriteString("value", string.Empty);
}
bsonWriter.WriteEndDocument();
}
private static void SerializeDataRecordInt(BsonWriter bsonWriter, object value)
{
var intRecord = (DataRecordInt) value;
bsonWriter.WriteStartDocument();
var keyValue = intRecord.Key;
bsonWriter.WriteString("key", string.IsNullOrEmpty(keyValue) ? string.Empty : keyValue);
bsonWriter.WriteInt32("value", intRecord.Value);
bsonWriter.WriteEndDocument();
}
}
这里也问了:https://groups.google.com/forum/#!topic/mongodb-user/iOeEXbUYbo4
我认为在这种情况下您最好的选择是使用自定义鉴别器约定。您可以在此处查看示例:https://github.com/mongodb/mongo-csharp-driver/blob/v1.x/MongoDB.DriverUnitTests/Samples/MagicDiscriminatorTests.cs。虽然此示例基于文档中是否存在字段,但您可以轻松地将其基于字段的类型(BsonType.Int32、BsonType.Date 等...)。
根据@Craig Wilson 的回答,要摆脱所有歧视,您可以:
public class NoDiscriminatorConvention : IDiscriminatorConvention
{
public string ElementName => null;
public Type GetActualType(IBsonReader bsonReader, Type nominalType) => nominalType;
public BsonValue GetDiscriminator(Type nominalType, Type actualType) => null;
}
并注册:
BsonSerializer.RegisterDiscriminatorConvention(typeof(BaseEntity), new NoDiscriminatorConvention());
我在向数据库添加 Dictionary 和 List 实体时出现了这个问题。以下 link 在这方面帮助了我:。对于你的情况,我建议按照上面给出的link,如下:
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
[JsonConverter(typeof(DataRecordConverter))]
public abstract class DataRecord
{
public string Key { get; set; }
public string DataRecordType {get;}
}
public class DataRecordInt : DataRecord
{
public int Value { get; set; }
public override string DataRecordType => "Int"
}
public class DataRecordDateTime : DataRecord
{
public DateTime? Value { get; set; }
public override string DataRecordType => "DateTime"
}
public class DataRecordConverter: JsonConverter
{
public override bool CanWrite => false;
public override bool CanRead => true;
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DataRecord);
}
public override void WriteJson(
JsonWriter writer,
object value,
JsonSerializer serializer)
{}
public override object ReadJson(
JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
var jsonObject = JObject.Load(reader);
var dataRecord = default(DataRecord);
switch (jsonObject["DataRecordType"].Value<string>())
{
case "Int":
dataRecord = new DataRecordInt();
break;
case "DateTime":
dataRecord = new DataRecordDataTime();
break;
}
serializer.Populate(jsonObject.CreateReader, dataRecord);
return dataRecord;
}
}
[BsonCollectionName("calls")]
[BsonIgnoreExtraElements]
public class Call
{
[BsonId]
public CallId _id { get; set; }
[JsonIgnore]
[BsonElement("responses")]
[BsonIgnoreIfNull]
public BsonArray Responses { get; set; }
}
您可以使用以下方式添加填充 BsonArray:
var jsonDoc = JsonConvert.SerializeObject(source);
var bsonArray = BsonSerializer.Deserialize<BsonArray>(jsonDoc);
现在,您可以使用以下方法从 Mongo 获取反序列化列表:
var bsonDoc = BsonExtensionMethods.ToJson(source);
var dataRecordList = JsonConvert.DeserializeObject<List<DataRecord>>(bsonDoc, new DataRecordConverter())
希望这对您有所帮助,再次感谢 。
情况: 语言:C# 使用 C# 驱动程序 我有一个包含 属性 列表的模型。该列表可以包含全部继承 BaseModelClass 的 3 个不同模型之一。为了帮助序列化这种情况,Mongo 添加了 _t 以识别实际使用的模型。对我们来说这是一个问题,因为 _t 占用了 space 的数量。我是一个低级开发人员,我要求更多 space 和 ram,他们告诉我无需额外的 space 即可解决它。所以我坐下来编写一个自定义序列化程序来处理不同的类型,而无需将 _t 写入 BSONDocument。在我开始对序列化进行单元测试之前,我认为一切都很好。我开始 "ReadEndArray can only be called when ContextType is Array, not when ContextType is Document."
非常感谢任何意见或建议。
这是我到目前为止的代码...
<----------Collection型号-------------------->
[BsonCollectionName("calls")]
[BsonIgnoreExtraElements]
public class Call
{
[BsonId]
public CallId _id { get; set; }
[BsonElement("responses")]
[BsonIgnoreIfNull]
public IList<DataRecord> Responses { get; set; }
}
<------------基础数据记录---------------->
[BsonSerializer(typeof(DataRecordSerializer))]
public abstract class DataRecord
{
[BsonElement("key")]
public string Key { get; set; }
}
<------------实际数据记录示例---------------->
[BsonSerializer(typeof(DataRecordSerializer))]
public class DataRecordInt : DataRecord
{
[BsonElement("value")]
public int Value { get; set; }
}
[BsonSerializer(typeof(DataRecordSerializer))]
public class DataRecordDateTime : DataRecord
{
[BsonElement("value")]
public DateTime? Value { get; set; }
}
<----------------触发解串器的单元测试---------------->
//Arrange
var bsonDocument = TestResources.SampleCallJson;
//Act
var result = BsonSerializer.Deserialize<Call>(bsonDocument);
//Assert
Assert.IsTrue(true);
<----------------序列化器---------------->
public class DataRecordSerializer : IBsonSerializer
{
public object Deserialize(BsonReader bsonReader, Type nominalType, IBsonSerializationOptions options)
{
//Entrance Criteria
if(nominalType != typeof(DataRecord)) throw new BsonSerializationException("Must be of base type DataRecord.");
if(bsonReader.GetCurrentBsonType() != BsonType.Document) throw new BsonSerializationException("Must be of type Document.");
bsonReader.ReadStartDocument();
var key = bsonReader.ReadString("key");
bsonReader.FindElement("value");
var bsonType = bsonReader.CurrentBsonType;
if (bsonType == BsonType.DateTime)
{
return DeserializeDataRecordDateTime(bsonReader, key);
}
return bsonType == BsonType.Int32 ? DeserializeDataRecordInt(bsonReader, key) : DeserializeDataRecordString(bsonReader, key);
}
public object Deserialize(BsonReader bsonReader, Type nominalType, Type actualType, IBsonSerializationOptions options)
{
//Entrance Criteria
if (nominalType != typeof (DataRecord)) throw new BsonSerializationException("Must be of base type DataRecord.");
if (bsonReader.GetCurrentBsonType() != BsonType.Document) throw new BsonSerializationException("Must be of type Document.");
bsonReader.ReadStartDocument(); // Starts Reading and is able to pull data fine through this and the next few lines of code.
var key = bsonReader.ReadString("key");
if (actualType == typeof(DataRecordDateTime))
{
return DeserializeDataRecordDateTime(bsonReader, key);
}
return actualType == typeof(DataRecordInt) ? DeserializeDataRecordInt(bsonReader, key) : DeserializeDataRecordString(bsonReader, key); // Once it tries to return I am getting the following Error: ReadEndArray can only be called when ContextType is Array, not when ContextType is Document.
}
public IBsonSerializationOptions GetDefaultSerializationOptions()
{
return new DocumentSerializationOptions
{
AllowDuplicateNames = false,
SerializeIdFirst = false
};
}
public void Serialize(BsonWriter bsonWriter, Type nominalType, object value, IBsonSerializationOptions options)
{
var currentType = value.GetType();
if (currentType == typeof (DataRecordInt))
{
SerializeDataRecordInt(bsonWriter, value);
return;
}
if (currentType == typeof(DataRecordDateTime))
{
SerializeDataRecordDateTime(bsonWriter, value);
return;
}
if (currentType == typeof(DataRecordString))
{
SerializeDataRecordString(bsonWriter, value);
}
}
private static object DeserializeDataRecordString(BsonReader bsonReader, string key)
{
var stringValue = bsonReader.ReadString();
var isCommentValue = false;
if (bsonReader.FindElement("iscomment"))
{
isCommentValue = bsonReader.ReadBoolean();
}
return new DataRecordString
{
Key = key,
Value = stringValue,
IsComment = isCommentValue
};
}
private static object DeserializeDataRecordInt(BsonReader bsonReader, string key)
{
var intValue = bsonReader.ReadInt32();
return new DataRecordInt
{
Key = key,
Value = intValue
};
}
private static object DeserializeDataRecordDateTime(BsonReader bsonReader, string key)
{
var dtValue = bsonReader.ReadDateTime();
var dateTimeValue = new BsonDateTime(dtValue).ToUniversalTime();
return new DataRecordDateTime
{
Key = key,
Value = dateTimeValue
};
}
private static void SerializeDataRecordString(BsonWriter bsonWriter, object value)
{
var stringRecord = (DataRecordString) value;
bsonWriter.WriteStartDocument();
var keyValue = stringRecord.Key;
bsonWriter.WriteString("key", string.IsNullOrEmpty(keyValue) ? string.Empty : keyValue);
var valueValue = stringRecord.Value;
bsonWriter.WriteString("value", string.IsNullOrEmpty(valueValue) ? string.Empty : valueValue);
bsonWriter.WriteBoolean("iscomment", stringRecord.IsComment);
bsonWriter.WriteEndDocument();
}
private static void SerializeDataRecordDateTime(BsonWriter bsonWriter, object value)
{
var dateRecord = (DataRecordDateTime) value;
var millisecondsSinceEpoch = dateRecord.Value.HasValue
? BsonUtils.ToMillisecondsSinceEpoch(new DateTime(dateRecord.Value.Value.Ticks, DateTimeKind.Utc))
: 0;
bsonWriter.WriteStartDocument();
var keyValue = dateRecord.Key;
bsonWriter.WriteString("key", string.IsNullOrEmpty(keyValue) ? string.Empty : keyValue);
if (millisecondsSinceEpoch != 0)
{
bsonWriter.WriteDateTime("value", millisecondsSinceEpoch);
}
else
{
bsonWriter.WriteString("value", string.Empty);
}
bsonWriter.WriteEndDocument();
}
private static void SerializeDataRecordInt(BsonWriter bsonWriter, object value)
{
var intRecord = (DataRecordInt) value;
bsonWriter.WriteStartDocument();
var keyValue = intRecord.Key;
bsonWriter.WriteString("key", string.IsNullOrEmpty(keyValue) ? string.Empty : keyValue);
bsonWriter.WriteInt32("value", intRecord.Value);
bsonWriter.WriteEndDocument();
}
}
这里也问了:https://groups.google.com/forum/#!topic/mongodb-user/iOeEXbUYbo4
我认为在这种情况下您最好的选择是使用自定义鉴别器约定。您可以在此处查看示例:https://github.com/mongodb/mongo-csharp-driver/blob/v1.x/MongoDB.DriverUnitTests/Samples/MagicDiscriminatorTests.cs。虽然此示例基于文档中是否存在字段,但您可以轻松地将其基于字段的类型(BsonType.Int32、BsonType.Date 等...)。
根据@Craig Wilson 的回答,要摆脱所有歧视,您可以:
public class NoDiscriminatorConvention : IDiscriminatorConvention
{
public string ElementName => null;
public Type GetActualType(IBsonReader bsonReader, Type nominalType) => nominalType;
public BsonValue GetDiscriminator(Type nominalType, Type actualType) => null;
}
并注册:
BsonSerializer.RegisterDiscriminatorConvention(typeof(BaseEntity), new NoDiscriminatorConvention());
我在向数据库添加 Dictionary
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
[JsonConverter(typeof(DataRecordConverter))]
public abstract class DataRecord
{
public string Key { get; set; }
public string DataRecordType {get;}
}
public class DataRecordInt : DataRecord
{
public int Value { get; set; }
public override string DataRecordType => "Int"
}
public class DataRecordDateTime : DataRecord
{
public DateTime? Value { get; set; }
public override string DataRecordType => "DateTime"
}
public class DataRecordConverter: JsonConverter
{
public override bool CanWrite => false;
public override bool CanRead => true;
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DataRecord);
}
public override void WriteJson(
JsonWriter writer,
object value,
JsonSerializer serializer)
{}
public override object ReadJson(
JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
var jsonObject = JObject.Load(reader);
var dataRecord = default(DataRecord);
switch (jsonObject["DataRecordType"].Value<string>())
{
case "Int":
dataRecord = new DataRecordInt();
break;
case "DateTime":
dataRecord = new DataRecordDataTime();
break;
}
serializer.Populate(jsonObject.CreateReader, dataRecord);
return dataRecord;
}
}
[BsonCollectionName("calls")]
[BsonIgnoreExtraElements]
public class Call
{
[BsonId]
public CallId _id { get; set; }
[JsonIgnore]
[BsonElement("responses")]
[BsonIgnoreIfNull]
public BsonArray Responses { get; set; }
}
您可以使用以下方式添加填充 BsonArray:
var jsonDoc = JsonConvert.SerializeObject(source);
var bsonArray = BsonSerializer.Deserialize<BsonArray>(jsonDoc);
现在,您可以使用以下方法从 Mongo 获取反序列化列表:
var bsonDoc = BsonExtensionMethods.ToJson(source);
var dataRecordList = JsonConvert.DeserializeObject<List<DataRecord>>(bsonDoc, new DataRecordConverter())
希望这对您有所帮助,再次感谢