C# driver, property of type implementing IList not saving

347 views
Skip to first unread message

Nik Coughlin

unread,
Jan 24, 2011, 1:01:09 AM1/24/11
to mongodb-user
Changed the persistence layer of my web app to MongoDb using the C#
drivers from the MongoDb site. Was pleasantly surprised to find all of
my tests passing... except for one class. One of its properties is a
type that implements IList and for some reason it doesn't save its
items.

I've built a minimal test case to illustrate. Here's the test code to
create and save the parent object:

var fooCollection = database.GetCollection<Foo>( typeof( Foo ).Name );
var foo = new Foo {Id = "Root"};
foo.Foos.Add( new Foo{ Id = "Child" } );
fooCollection.Save( foo );

If I declare Foo.Foos as being List<Foo> it works:

public class Foo {
public Foo() {
Foos = new List<Foo>();
}
public List<Foo> Foos;
public string Id;
}

The (correct) result:

{ "_id" : "root", "Foos" : [ { "Foos" : [], "_id" : "child" } ] }

However what I need is this:

public class Foo {
public Foo() {
Foos = new FooList();
}
public FooList Foos;
public string Id;
}

public class FooList : IList<Foo> {
//IList implementation omitted for brevity
}

The (incorrect) result is:

{ "_id" : "root", "Foos" : { "Capacity" : 4 } }

Note that it has nothing to do with my IList implementation as the
results are the same if I use FooList : List<Foo>.

I'm presuming that the BSON serializer is getting confused? I looked
at the documentation on discriminators, which led me to think that
this might help:

BsonClassMap.RegisterClassMap<List<Foo>>( cm => {
cm.AutoMap();
cm.SetIsRootClass( true );
} );
BsonClassMap.RegisterClassMap<FooList>();

I still don't get my items saved though, ends up looking like this:

{ "_id" : "root", "Foos" : { "_t" : [ "List`1", "FooList" ],
"Capacity" : 4 } }

How can I save FooList correctly?

Nat

unread,
Jan 24, 2011, 2:26:11 AM1/24/11
to mongodb-user
Which version of driver do you use?

Nat

unread,
Jan 24, 2011, 2:43:20 AM1/24/11
to mongodb-user
with 0.9, it seems to work correctly...

public class Foo
{
public Foo()
{
Foos = new List<Foo>();
}
public List<Foo> Foos;
[BsonId]
public string Id;
}

[Test]
public void Test2()
{
BsonClassMap.RegisterClassMap<List<Foo>>(cm =>
{
cm.AutoMap();
cm.SetIsRootClass(true);
});
var foo = new Foo { Id = "Root" };
foo.Foos.Add(new Foo { Id = "Child" });
var json = foo.ToJson();
var expected = "{ 'Foos' : [{ 'Foos' : [], '_id' :
'Child' }], '_id' : 'Root' }".Replace("'", "\"");
var bson = foo.ToBson();
Assert.AreEqual(expected, json);

var rehydrated = BsonSerializer.Deserialize<Foo>(bson);
Assert.IsInstanceOf<List<Foo>>(rehydrated.Foos);
Assert.IsTrue(bson.SequenceEqual(rehydrated.ToBson()));
}

Anyway, it looks like you are trying to implement some sort of self
joining. You might want to read
http://www.mongodb.org/display/DOCS/Trees+in+MongoDB

Robert Stam

unread,
Jan 24, 2011, 10:05:15 AM1/24/11
to mongodb-user
I'm assuming you are using an older version of the driver, but if not
we can look into this further.

A note about your Foo class containing a list of Foos: this is OK as
long as the object graph is a tree. If any of those nested Foos are
references to Foos further up on the tree you will get a
StackOverflowException when serializing. The BsonSerializer does not
handle self referencing object graphs.
> joining. You might want to readhttp://www.mongodb.org/display/DOCS/Trees+in+MongoDB

Nik Coughlin

unread,
Jan 24, 2011, 2:38:06 PM1/24/11
to mongodb-user
Hi there,

On Jan 24, 8:43 pm, Nat <nat.lu...@gmail.com> wrote:
> with 0.9, it seems to work correctly...
>
>         public class Foo
>         {
>             public Foo()
>             {
>                 Foos = new List<Foo>();
>             }
>             public List<Foo> Foos;
>             [BsonId]
>             public string Id;
>         }

If you have a look at my example again you'll see I give this as being
the case that *does* work :)

The 2nd example is the one that doesn't, where instead of List<Foo> I
use FooList : IList<Foo>

> Anyway, it looks like you are trying to implement some sort of self
> joining. You might want to readhttp://www.mongodb.org/display/DOCS/Trees+in+MongoDB

Thanks for that link! Yes, the original class that I'm having trouble
with is a tree. But I'd rather not change my tree code if possible, it
should work fine.

Robert Stam

unread,
Jan 24, 2011, 2:40:50 PM1/24/11
to mongodb-user
Can you confirm which version of the driver you are using?

Robert Stam

unread,
Jan 24, 2011, 3:02:04 PM1/24/11
to mongodb-user
I see now that your question is trickier than it first seemed...

The problem is that the BsonSerializer always has to use the *actual*
type of the object when serializing it (in order to not omit
information). Your *actual* type is FooList. but it sounds like you
are expecting it to be serialized as an instance of IList<Foo>. But
that's not how it works.

The problem is that now knowing any better how to serialize the
FooList class the serializer creates a default BsonClassMap which just
serializes the public read/writer properties (of which there is one:
Capacity).

I'll start by giving you one way to solve this that might not be the
right solution: if you want your FooList to be serialized as if it
were an IList<Foo> you can set up the class map to do that. Here's a
link to some sample code:

http://www.pastie.org/1493711

The important new lines are:

static Foo() {
BsonClassMap.RegisterClassMap<Foo>(cm => {
cm.AutoMap();

cm.GetMemberMap("Foos").SetSerializer(BsonSerializer.LookupSerializer(typeof(IList<Foo>)));
});
}

The problem with this is that not having your FooList implementation I
don't know if deserialization will work also. Perhaps you can test and
report back.

Nik Coughlin

unread,
Jan 24, 2011, 3:20:08 PM1/24/11
to mongodb-user
Hi Robert,

On Jan 25, 4:05 am, Robert Stam <rstam10...@gmail.com> wrote:
> I'm assuming you are using an older version of the driver, but if not
> we can look into this further.

Sorry, I should have said.

Driver is 0.9.0.3992

MongoDb Win32 x86-64 1.6.5

> A note about your Foo class containing a list of Foos: this is OK as
> long as the object graph is a tree. If any of those nested Foos are
> references to Foos further up on the tree you will get a
> StackOverflowException when serializing. The BsonSerializer does not
> handle self referencing object graphs.

Yeah, the above is a minimal example, it exhibits the same problem as
my full tree, which is more like (still somewhat elided):

public interface INode<T> {
INodeList<T> Children { get; set; }
INode<T> Parent { get; set; }
T Value { get; set; }
...
}

public interface INodeList<T> : IList<INode<T>> {
INode<T> Parent { get; set; }
INode<T> Add( T value );
...
}

The implementation ensures that it can't be self-referencing.

Also, I just tried serializing a FooList on it's own and that didn't
work either. So it turns out we can reduce the minimal test case
further:

public class FooList : List<string> {
public string Id;
}

var fooListCollection =
database.GetCollection<FooList>( typeof( FooList ).Name );
var fooList = new FooList{ Id = "fooList" };
fooList.Add( "fooListItem" );
fooListCollection.Save( fooList );

{ "_id" : "fooList", "Capacity" : 4 }

Robert Stam

unread,
Jan 24, 2011, 3:23:16 PM1/24/11
to mongodb-user
I think when you wrote your last message you probably had not yet seen
my most recent one. Can you follow up again after reading that?
Thanks.

Nik Coughlin

unread,
Jan 24, 2011, 3:23:40 PM1/24/11
to mongodb-user
Hi, sorry, looks like you posted while I was composing my previous
reply. Thanks for all your help, I'll give that a shot.

Thanks again

Nik Coughlin

unread,
Jan 24, 2011, 3:38:02 PM1/24/11
to mongodb-user
On Jan 25, 9:23 am, Robert Stam <rstam10...@gmail.com> wrote:
> I think when you wrote your last message you probably had not yet seen
> my most recent one. Can you follow up again after reading that?
> Thanks.

OK... using the code provided at the Pastie link:

{ "_id" : "Root", "Foos" : { "Capacity" : 4 } }

I changed this line because it throws an exception otherwise (it was
IList rather than List):

cm.GetMemberMap( "Foos" ).SetSerializer( BsonSerializer.LookupSerializer( typeof( List<Foo> ) ) );

Other than that I used your code as provided, still no luck

Robert Stam

unread,
Jan 24, 2011, 4:06:01 PM1/24/11
to mongodb-user
I am able to reproduce what you are describing with 0.9, but it is
working correctly with the latest code.

You could build the driver yourself from the latest code in github, or
wait for 0.11 which is coming it very soon.

Nik Coughlin

unread,
Jan 24, 2011, 4:17:06 PM1/24/11
to mongodb-user
Wonderful, I'll grab the newest source. Thanks a million for all your
help.

I was even starting to think things like "I wonder how hard it would
be to write my own serializer, I'll just go have a look at the
DataContractSerializer source from Mono" :)
Reply all
Reply to author
Forward
0 new messages