BsonClassMap.IsClassMapRegistered might not work correctly

980 views
Skip to first unread message

nuha

unread,
Jan 4, 2012, 8:44:09 PM1/4/12
to mongodb-user
I have a class with this code in the constructor :
---
public MongoDbFooRepository(ILog logger)
if (!BsonClassMap.IsClassMapRegistered(typeof(Foo)))
{
try
{
BsonClassMap.RegisterClassMap<Foo>(
cm =>
{
cm.AutoMap();
cm.SetIgnoreExtraElements(true);
});
}
catch (Exception exception)
{
Logger.Info("Registration fo class map for Foo failed", exception);
}
}

----
Under normal user traffic, it seems to work fine.
However a certain unit test that uses that class fails consistently
when I run "all" unit tests. That same unit test succeeds when I run
only the one unit test that fails.
The failure message is :
----
System.IO.FileFormatException: Element 'Syncs' does not match any
field or property of class ... Foo.
at
MongoDB.Bson.Serialization.BsonClassMapSerializer.Deserialize(BsonReader
bsonReader, Type nominalType, Type actualType,
IBsonSerializationOptions options)
at
MongoDB.Bson.Serialization.BsonClassMapSerializer.Deserialize(BsonReader
bsonReader, Type nominalType, IBsonSerializationOptions options)
at MongoDB.Bson.Serialization.BsonSerializer.Deserialize(BsonReader
bsonReader, Type nominalType, IBsonSerializationOptions options)
at MongoDB.Driver.Internal.MongoReplyMessage`1.ReadFrom(BsonBuffer
buffer, IBsonSerializationOptions serializationOptions)
at MongoDB.Driver.Internal.MongoConnection.ReceiveMessage[TDocument]
(BsonBinaryReaderSettings readerSettings, IBsonSerializationOptions
serializationOptions)
at MongoDB.Driver.MongoCursorEnumerator`1.GetReply(MongoConnection
connection, MongoRequestMessage message)
at MongoDB.Driver.MongoCursorEnumerator`1.GetMore()
at MongoDB.Driver.MongoCursorEnumerator`1.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
----
Since I'm using said object and calling the
BsonClassMap.RegisterClassMap() with SetIgnoreExtraElements(true) my
expectation was that this should not occur . The element 'Syncs'
should have been ignored silently.

Is this a misunderstanding of the class mapper or could threading /
dispose() issues with rapid succession?
Anyone encounter such issue?
MongoDB c# driver 1.3.1.4349

Nat

unread,
Jan 4, 2012, 9:38:59 PM1/4/12
to mongod...@googlegroups.com
Did you make sure that you run classmap registration before executing code in that failed unit test?
--
You received this message because you are subscribed to the Google Groups "mongodb-user" group.
To post to this group, send email to mongod...@googlegroups.com.
To unsubscribe from this group, send email to mongodb-user...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/mongodb-user?hl=en.

Robert Stam

unread,
Jan 4, 2012, 10:26:55 PM1/4/12
to mongod...@googlegroups.com
The best place to call RegisterClassMap<Foo> is in a static constructor for Foo. If you can't do that (perhaps you can't modify Foo.cs) then you must call RegisterClassMap in some code path that guarantees that RegisterClassMap is called BEFORE any other code attempts to serialize or deserialize a Foo.

The way you have it now if any other code path serializes or deserializes a Foo before this code runs it will result in AutoMap being called for Foo and then IsClassMapRegistered will return true in this code. That could explain why it doesn't always seem to ignore the extra elements.

It is also not thread-safe the way you have it coded now (maybe that's why you have a try/catch?).

Kazi Manzur Rashid

unread,
Jan 5, 2012, 4:12:09 AM1/5/12
to mongodb-user
Okay I was under the assumption that I should check whether the Map
(e.g. WidgetMap : BsonClas<Widget>) needs to be registered. not the
actual Domain Object, am I missing something?

Robert Stam

unread,
Jan 5, 2012, 9:54:40 AM1/5/12
to mongod...@googlegroups.com
The call to IsClassMapRegistered was coded correctly. The argument is supposed to be the type of your domain object, and it checks whether a class map for that domain class has been registered.

The issue isn't with the way the call was coded, but rather with the timing of when it actually executed. It might return either true or false depending on whether any other execution path before this one had resulted in Foo being automaped.

nuha

unread,
Jan 5, 2012, 12:26:25 PM1/5/12
to mongodb-user
I would assume that the call to IsClassMapRegistered would return true
only if it is registered, and never return false if it is already
registered.

The instance (non static) constructor of the class is called before I
use the class - there's no other constructor for that class in my code
currently. So the IsClassMapRegistered followed by the Register call
is done there.
It's not totaly thread safe, 2 competing threads may reach the IsCmReg
and both get a 'false', then attempt to reg and one would win over the
other, provoking an exception (caught though).
Still, I don't see how with the current code the scenario where a
classmap is not registered before the use of that type.

Robert Stam

unread,
Jan 5, 2012, 12:43:55 PM1/5/12
to mongod...@googlegroups.com
True, IsClassMapRegistered only returns true if there is already a class map registered for Foo.

So if IsClassMapRegistered is unexpectedly returning true for you it is because something else in your code has already resulted in a class map for Foo being registered (perhaps as a result of Automapping).

Here's some sample code that causes Foo to be Automapped and a class map registered for Foo:

    Foo foo = new Foo { ... };
    var json = foo.ToJson();

    // IsClassMapRegistered is going to return true because of Foo was Automapped when ToJson was called
    if (BsonClassMap.IsClassMapRegistered(typeof(Foo))) {
        Console.WriteLine("A class map for Foo has been registered");
    }

ToJson needs a class map for Foo in order to serialize an instance of Foo to JSON. Since the class map doesn't exist it is automatically created and registered.

What you need to do in your code is move the call to RegisterClassMap to a code path that executes earlier, BEFORE any other serialization or deserialization of Foo happens.

I would move the call to RegisterClassMap to the static constructor of Foo if possible.

Robert Stam

unread,
Jan 5, 2012, 12:53:09 PM1/5/12
to mongod...@googlegroups.com
If you build the C# driver yourself you can set a breakpoint in the C# driver and find out where in your code the unexpected call to RegisterClassMap for Foo is coming from.

Set a conditional breakpoint in:

- open BsonClassMap.cs
- set a break point on the first line of RegisterClassMap (be sure to pick the overload that does the actual work)
- set the condition of the breakpoint to: classMap.ClassType.Name == "Foo"

Then look at the call stack to figure out where you came from.

nuha

unread,
Jan 16, 2012, 9:52:45 PM1/16/12
to mongodb-user
Good idea!
I'll try and arrange that scenario.
Reply all
Reply to author
Forward
0 new messages