[CSHARP-50] Error in MongoDB.Bson.BsonWriter.TranslateToBsonType() when translating Document fields of type System.Byte Created: 08/Jul/10  Updated: 19/Oct/16  Resolved: 11/Nov/10

Status: Closed
Project: C# Driver
Component/s: SAMUS
Affects Version/s: None
Fix Version/s: None

Type: Bug Priority: Major - P3
Reporter: Anye Mercy Assignee: Sam Corder
Resolution: Done Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified
Environment:

VS 2008 / .NET 3.5



 Description   

The method TranslateToBsonType(object obj) in MongoDB.Bson.BsonWriter does not handle objects of type System.Byte, I am trying to convert a business object to a MongoDB document, the object has fields of type Byte on them, and these are choking. I've given some details below but it really looks like all you should need to fix this is to add this line to the TranslateToBsonType() method among all the other type checks:

if(type == typeof(Byte))
return BsonType.Binary;

My application code:
var doctable = db.GetCollection("CaseDocuments");
foreach (FullDocumentInfo doc in docs)

{ doctable.Insert(doc.ToDocument()); //error is thrown deep inside the Insert(). }

ToDocument() is the extension method described here http://www.highoncoding.com/Articles/680_Implementing_Business_Object_to_Documents_Converter_for_MongoDb.aspx
I implemented it in a DocConverter class:
public static class DocConverter
{
public static T ToClass<T>(this Document source)

{ if (source == null) throw new ArgumentNullException("Document is null!"); return new JavaScriptSerializer().Deserialize<T>(source.ToString()); }

public static Document ToDocument(this object source)

{ var document = SerializeMember(source) as Document; return document; }

private static object SerializeMember(object source)
{
// get the properties

if (!Type.GetTypeCode(source.GetType()).Equals(TypeCode.Object))
return source;

// if the object is IEnumerable

var enumerable = source as IEnumerable;

if (enumerable != null)
{
var documents = new List<Object>();

foreach (var doc in enumerable)

{ documents.Add(SerializeMember(doc)); }

return documents.ToArray();
}

var properties = source.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);

var document = new Document();

foreach (var property in properties)

{ var propertyValue = property.GetValue(source, null); if (propertyValue == null) continue; document.Add(property.Name, SerializeMember(propertyValue)); }

return document;
}
}

Stack trace:
> MongoDB.dll!MongoDB.Bson.BsonWriter.TranslateToBsonType(object obj = 46) Line 612 C#
MongoDB.dll!MongoDB.Bson.BsonWriter.CalculateSize(object obj = 46) Line 325 + 0xb bytes C#
MongoDB.dll!MongoDB.Bson.BsonWriter.CalculateSizeObject(object obj =

{MongoDB.Serialization.Descriptors.ArrayDescriptor}

, System.Collections.Generic.IEnumerable<MongoDB.Bson.BsonProperty> propertys =

{MongoDB.Serialization.Descriptors.ArrayDescriptor.GetProperties}

) Line 485 + 0x24 bytes C#
MongoDB.dll!MongoDB.Bson.BsonWriter.CalculateSize(System.Collections.IEnumerable enumerable =

{object[4]}) Line 503 + 0xe bytes C#
MongoDB.dll!MongoDB.Bson.BsonWriter.CalculateSize(object obj = {object[4]}

) Line 349 + 0x17 bytes C#
MongoDB.dll!MongoDB.Bson.BsonWriter.CalculateSizeObject(object obj =

{MongoDB.Serialization.Descriptors.DocumentPropertyDescriptor}, System.Collections.Generic.IEnumerable<MongoDB.Bson.BsonProperty> propertys = {System.Linq.Enumerable.WhereSelectEnumerableIterator<System.Collections.Generic.KeyValuePair<string,object>,MongoDB.Bson.BsonProperty>}) Line 485 + 0x24 bytes C#
MongoDB.dll!MongoDB.Bson.BsonWriter.CalculateSizeObject(object obj = {MongoDB.Serialization.Descriptors.DocumentPropertyDescriptor}

) Line 464 + 0xe bytes C#
MongoDB.dll!MongoDB.Bson.BsonWriter.CalculateSize(object obj) Line 347 + 0x39 bytes C#
MongoDB.dll!MongoDB.Protocol.InsertMessage.ChunkMessage(MongoDB.Bson.BsonWriter writer =

{MongoDB.Bson.BsonWriter}

) Line 73 + 0xd bytes C#
MongoDB.dll!MongoDB.Protocol.InsertMessage.Write(System.IO.Stream stream =

{System.Net.Sockets.NetworkStream}

) Line 56 + 0xb bytes C#
MongoDB.dll!MongoDB.Connections.Connection.SendMessageCore(MongoDB.Protocol.IRequestMessage message =

{MongoDB.Protocol.InsertMessage}) Line 141 + 0x22 bytes C#
MongoDB.dll!MongoDB.Connections.Connection.SendMessage(MongoDB.Protocol.IRequestMessage message = {MongoDB.Protocol.InsertMessage}

, string database = "DocTest1") Line 126 + 0xb bytes C#
MongoDB.dll!MongoDB.MongoCollection<MongoDB.Document>.Insert<MongoDB.Document>(System.Collections.Generic.IEnumerable<MongoDB.Document> documents =

{MongoDB.Document[1]}

) Line 333 + 0x3c bytes C#
MongoDB.dll!MongoDB.MongoCollection<MongoDB.Document>.Insert<System.Collections.Generic.KeyValuePair<string,object>>(System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string,object>> documents) Line 306 + 0xac bytes C#
MongoDB.dll!MongoDB.MongoCollection.Insert(MongoDB.Document document) Line 224 + 0x1b bytes C#
Simple.exe!Simple.DocTest1.CopyDocData() Line 38 + 0x29 bytes C#
Simple.exe!Simple.MainClass.Main(string[] args =

{string[0]}

) Line 27 + 0xa bytes C#



 Comments   
Comment by Steve Wagner [ 11/Nov/10 ]

Can you check if this still happens in our latest master? If yes please report here back https://github.com/mongodb-csharp/mongodb-csharp/issues.

Comment by Anye Mercy [ 12/Jul/10 ]

I actually hit another bug when I use the strongly typed version – I suspect it is an artifact of the first bug but it does bring up an interesting point.

Basically what is happening is that since currently the Byte can't be translated, that field is being saved to the database as null.

When I try to load the Document using the typed GetCollection, it calls (after a fashion) the MemberMapBase to start setting properties. When it comes to the null byte field, it casts it as an object[0] - then calls the setter on the underlying object – but Byte is not a valid Byte so it throws an InvalidCastException.

The actual error is thrown in MemberMapBase.SetValue at _setter(instance,value)

I thought about this a while trying to decide what the proper behavior should be and it really comes down to whether the MemberMapBase is only used when constructing a new object or whether it can be used on an existing object. If we know that it only is ever used to create objects then I would think the proper behavior would be to skip nulls, leaving the object's properties in the default state. If we don't know this then it is not a good idea to try to skip nulls in the driver because you can't tell the difference between an intentional null and a just-not-populated null that you wouldn't want to replace existing data.

PS - Thanks for the quick response!

This can be worked around on the client side by making sure the setters all handle nulls more gracefully but for large applications this would be a big detriment to conversion because all the business objects using Bytes or other non-nullable types would be potentially affected. (My application is a production application targeting SQL Server, and I'm working on a proof of concept to see if MongoDB will perform better in our problem space).

I also was able to work around it by using the non-typed GetCollection and then using my ToClass<T> to rehydrate the object from the database, this method skipped the nulls.

Comment by Steve Wagner [ 09/Jul/10 ]

Ok i am looking at this.

By the way. Why do you dont use the much more powerful typed collection in the latest version of the driver?

var docTable = db.GetCollection<FullDocumentInfo>("CaseDocuments");
foreach (FullDocumentInfo doc in docs)
{
doctable.Insert(doc);
}

Generated at Wed Feb 07 21:35:40 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.