Using default constructor when deserializing

693 views
Skip to first unread message

Bas Geertsema

unread,
May 7, 2012, 7:12:42 AM5/7/12
to mongodb-user
I recently upgraded my C# mongodb driver to the latest release. My
previous version was a couple of months old. It seems that with the
latest release the default (no parameters) constructors are no longer
called upon deserialization. Is this correct?

I used to set default values in the constructor, however in the latest
release these fields are null after deserialization. I.e. when a
property is present in the object but not in the fetched bson document
it is set to null rather than having the value set in the constructor.

I have trouble finding out when or how this has changed. And whether I
can overrule this behaviour somehow. Can someone shed a light on this?

Thanks,

Bas

craiggwilson

unread,
May 7, 2012, 10:12:42 AM5/7/12
to mongod...@googlegroups.com
I am unable to reproduce this behaviour.  In addition, the code checks for a default, public or nonpublic constructor and uses that if it exists.  I used the below program to test.  Would you mind posting your code (or a snippet) so that we can find out what is going on.

    public static class Program
    {
        private class Test
        {
            private string _initialized;
 
            public ObjectId Id { getset; }
 
            public bool Value { getset; }
 
            public Test()
            {
                _initialized = "yes";
            }
 
            public override string ToString()
            {
                return _initialized;
            }
        }
 
        public static void Main()
        {
            var server = MongoServer.Create();
            var db = server.GetDatabase("test");
            var collection = db.GetCollection<Test>("initialize");
 
            var test = new Test();
 
            collection.Insert(test);
 
            test = collection.FindOne();
 
            Console.WriteLine(test);
        }
 
    }

Bas Geertsema

unread,
May 7, 2012, 11:39:35 AM5/7/12
to mongod...@googlegroups.com
I was perhaps a bit unclear about the issue. It occurs when a public property is not set at all in the database, but the public property is set in the constructor (on a sidenote: to a value that is not constant at compile time, which is why it is not possible to use attributes on the property). When the document is fetched, the constructor is invoked, but after construction the public property is set to null again (probably because it does not exist in the source document in the database).

I have posted some code below to make things clear. This seems like a change in behaviour compared with the version about 5 months ago. I guess I could register my own class maps and specify the default values, but this seems a bit redundant. What is the rationale behind this behaviour? And is there a way that I can override this (using a custom convention?)

The test code:

private class Test
        {
            public string SetInDb { get; set; }

            public string NotSetInDb { get; set; }

            public ObjectId Id { get; set; }

            public bool Value { get; set; }

            public Test()
            {
                SetInDb = "test";
                NotSetInDb = "test";
            }
        }

        [TestMethod]
        public void InitializeTest()
        {
            var database = this.GetDatabase();
            var collection = database.GetCollection("initialize");
            collection.RemoveAll();

            var document = new BsonDocument();            
            document["_id"] = ObjectId.GenerateNewId();
            document["SetInDb"] = new Test().SetInDb;
            collection.Insert(document);

            var collectionTyped = database.GetCollection<Test>("initialize");
            var test = collectionTyped.FindOne();
            Assert.AreEqual("test", test.SetInDb);      // this assert is ok
            Assert.AreEqual("test", test.NotSetInDb);   // assert fails for this one
        }


Kind regards,

Bas



--
You received this message because you are subscribed to the Google Groups "mongodb-user" group.
To view this discussion on the web visit https://groups.google.com/d/msg/mongodb-user/-/iNk03NvoQPAJ.
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.



--
Dactulos

craiggwilson

unread,
May 7, 2012, 12:09:14 PM5/7/12
to mongod...@googlegroups.com
So, I've run your code and can reproduce what you are seeing.  What version of the driver are you upgrading from?  I'm not sure when the change happened, but if it was working like this before and isn't anymore, then it must have.

That being said, her is a question.  When do you want the property NotSetInDb to get persisted? (Never, Always, when it isn't default, etc...)  If always, then you can do a couple of things.  1) you can set the default value of the property such that when it doesn't exist in the database, it gets pulled in.  This can be done either via the BsonDefaultValueAttribute or by using the fluent syntax on the class map.  2) You can implement ISupportInitialize and use the EndInit method to set your value.

There is another attribute for BsonIgnoreIfDefault that won't persist the property if it the default value.  Also, there is BsonIgnore that won't ever persist the property.

Let me know if any of these is unacceptable and we'll try and figure something else out.
To unsubscribe from this group, send email to mongodb-user+unsubscribe@googlegroups.com.

For more options, visit this group at http://groups.google.com/group/mongodb-user?hl=en.

Bas Geertsema

unread,
May 8, 2012, 1:53:17 PM5/8/12
to mongod...@googlegroups.com
I have just executed the code with driver release v1.2 (the one I used) and I can confirm that it does not fail there (i.e. all asserts are true). So something indeed has changed. But let's move forward.

Let me start with my typical use case to make clear what I want to achieve:

The use case is that your domain entities are stored in the database. But typically your system will grow and more properties will be added to entities from time to time. The 'older' entities in the database will not have these newly added properties. This wasn't a problem though, because when these 'older' entities were fetched from the database for processing the default values were supplied for the non-existing properties and you end up with a object that behaves jus as you would have if you had constructed it in the code and then set the properties yourself. Especially in new projects where there is a lot of dynamic this is very convenient. I think that this use case is quite common and the current behaviour might surprise some developers.

One can argue that setting the properties to null is a more accurate reflection of the actual document in the database, but in my experience this is not really what you want in the majority of the cases.

Anyway, to answer your question: the property should always be serialized. I don't care if it happens to be the default or not. The problem is that I am using default values that are not constant at compile time. So any attributes are of no use as they require constants. Using the class map is an option (that is how I have solved it for now), but for some reason I cannot set in the static class constructor (the map already exists at that point), so I end up specifying default values for a class in   both the constructor and in another fairly unrelated initialization method, which is not ideal. 

I did not know about ISupportInitialize (is it in the documentation?), but this seems like a good option. There are only two methods to override so not too much noise is added to my class. I will try this.

But even then I still have to specify my default values at two locations which is a bit error-prone. A solution might be an (not yet existing) attribute like [BsonIgnoreIfNotSet] that specified that if this property is not set in the database document then no action must be taken (i.e. not overwritten by null). This way you wouldn't have to specify your default value twice.

Having said all that, I also want to mention that the C# driver is great to work with and I'm glad to see that there is so much active development in improving it :)

Kind regards,

Bas



To view this discussion on the web visit https://groups.google.com/d/msg/mongodb-user/-/4UraJyDPKjcJ.

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.

craiggwilson

unread,
May 8, 2012, 2:10:46 PM5/8/12
to mongod...@googlegroups.com
I understand your pain and apologize for the issue.  Just as a matter of curiosity, if your default values are not constant, where are they coming from?  Regarding ISupportInitialize, it was added a short while ago and I'm not sure if it made it into the documentation yet.

So, I think you've hinted at the proper solution which is to simply not apply default values unless they have been explicitly set using the attribute or the BsonClassMap.  Hence, the properties will not be touched and your code should run as you expect  I've added a bug to jira here: CSHARP-467 for this issue.  Please leave a comment there if you'd like to clarify the issue.

Bas Geertsema

unread,
May 8, 2012, 2:45:18 PM5/8/12
to mongod...@googlegroups.com
Thanks for the submission. And to answer your curiosity, I often use constant strings instead of enums. However I loose type-safety when I just use regular strings instead of enums. So I wrap my constant strings in 'typed string' classes. (for example: (OrderStatus)"Open"). So the default values are indeed almost constants, but not really to the compiler because the wrapper class needs to be constructed. I agree that this not very common pattern though and in general most default values will be primitives or constants.


Op dinsdag 8 mei 2012 20:10:46 UTC+2 schreef craiggwilson het volgende:
Reply all
Reply to author
Forward
0 new messages