[CSHARP-80] Return Local DateTime by default Created: 22/Oct/10  Updated: 20/Mar/14  Resolved: 30/Oct/10

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

Type: New Feature Priority: Major - P3
Reporter: Robert Stam Assignee: Robert Stam
Resolution: Done Votes: 1
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified


 Description   

Moved from: http://github.com/mongodb/mongo-csharp-driver/issues#issue/4

What the most .Net developers expect is that they can save a DateTime.Now to database, load it and get back the correct value.

Currently the driver returns UTC. Which forces the developers do always convert it to local date by hand which is very uncomfortable.

MongoDB-CSharp solves that by automatically convert the date to a local date (means the timezone where i am in currently). But the developer is able to deactivate this by configuration.

I think 95% of all developers are developing apps where they store dates only for the current timezone.



 Comments   
Comment by Robert Schooley [ 04/Dec/10 ]

Not to put words into Testo's mouth, but I don't believe either of us were looking to change how the data is stored.

Robert added the functionality to default to local time in the serialization process, however I am using the raw BsonDocuments, which doesn't support this feature. Not a big deal really.

PS: Thanks Robert for walking me through the above.

Comment by Ian Mercer [ 04/Dec/10 ]

Not that it affects the solution since the solution defaults to UTC but the original premise of this issue is at odds with database best practices. Most DateTimes can and should be stored in UTC and not local time because of the many issues that local time has (e.g. it goes backwards and repeats during DST changes) making it impossible to sort correctly for all times within a year.

Comment by Robert Stam [ 25/Nov/10 ]

It's no problem to have an Id property of type string. You just have to assign the unique values yourself.

I've modified the sample program at:

https://gist.github.com/714723

to use a string Id. I also made the following changes:

1. Replaced AutoMap with explicit mapping (useful when you want total control)
2. Changed the FindOne query to query on Id instead of Name

The "Unexpected element _id" issue should only show up in v0.7. Are you using the latest code from github?

For the model you describe (Model has Bar which has a Baz) it will be serialized using embedded documents, where the embedded Bar has ALL the properties of Bar (and in turn the embedded Baz has all the properties of Baz). That's just how serialization of embedded objects is supposed to work.

If that's now what you want, then you have to do something different. You could either:

1. Change your Model so that instead of having a Bar property you only have an Id for the Bar
2. Write some custom serialization code so that when a Model is saved you only save the Id and not the whole Bar object

In the second case it will be rather challenging to read a Model back in because you will only have the Id and somehow you have to rehydrate the entire Bar object, so this may not even be possible.

It's fine to use BsonDocument directly if that's what you want. But currently there is no control over how DateTime timezones are handled in a BsonDocument. That only applies to serialization of POCOs.

Regarding timezones, I highly recommend that you store all your values in UTC. If your application ever has to run in multiple timezones you pretty much have to go with UTC as a common denominator. Even in a single timezone going with UTC in your database can save you from daylight saving time issues.

Comment by Robert Schooley [ 25/Nov/10 ]

Thanks for the sample code. I have previously tried to go this route but ran into some issues:

1) My Id property does not know about the implementation type of ObjectId, it is a string.
ref: http://groups.google.com/group/mongodb-user/browse_thread/thread/16cd629cff9e6454/834503624196bf61?lnk=gst&q=blu#834503624196bf61

Is the proper setting: cm.MapIdProperty(c => c.Id);
I never got this to work, I get an exception: "Unexpected element: _id" Excepted

2) Allowing automatic parsing and populating of objects has me a little concerned.

I have added in more model classes, where Model has a Bar, and Bar can have a Baz, etc. When I save the Model class I get all of the Bar properties, even though they don't apply.
Reference: https://gist.github.com/715734

For example take saving a Blog Post. I could have all the Posts be a nested collection inside of the User document, but let's say I don't and I have them as top level collections due to other design factors. When I save the Post, I want to add the User.Id property. I don't want the entire User object saved into the Post. There are properties for Roles, Password, Email, etc. These won't be populated in the case of saving the Post, but they would be sent to the database and be just hanging out in there effectively creating noise, or worse potentially stale data if the fields were populated.

Using the BsonDocument directly has given me a lot of flexibility at the cost of a little development overhead. Am I misusing the library or just leaving a little convenience on the table?

Back to the DateTime challenge, is it possible to have the date time default to local using the BsonDocument approach? I can't think of a use case where I want UTC so I can probably go into the source and modify the default there but I would prefer a way to just globally change this setting and not have to customize it for each DateTime property in every Model.

Thanks.

Comment by Robert Stam [ 25/Nov/10 ]

There is no need to convert your Model instances to and from BsonDocument. You can save and read instances of Model directly to the collection.

I've modified your sample a bit to show what I mean:

https://gist.github.com/714723

I made some other minor changes to get everything to work:

  • Added an Id field to the model class
  • Moved the serialization initialization code to the top
  • Changed the class initializer argument to RegisterClassMap (see AutoMap and GetMemberMap)
  • Changed the collection variable to have a default document type of Model
  • Removed the code mapping to and from BsonDocument
Comment by Robert Schooley [ 24/Nov/10 ]

Thanks for the reply. I tried this out and did not seem to get it to work.

I forked your gist with an example of what I am trying to accomplish:
https://gist.github.com/714658

Comment by Robert Stam [ 21/Nov/10 ]

Sure. For anything that can be done with attributes there should be an equivalent way to do it using initialization code alone for those who wish to keep persistence details out of their model. Here's a link to a gist of a sample program:

https://gist.github.com/708380

The relevant lines are:

public class Model {
public DateTime DateTimeProperty

{ get; set; }

}

var localTime = new DateTimeSerializationOptions

{ Kind = DateTimeKind.Local }

;
BsonClassMap.RegisterClassMap<Model>(cm => {
cm.MapProperty(c => c.DateTimeProperty)
.SetSerializationOptions(localTime);
});

Comment by Robert Schooley [ 21/Nov/10 ]

Is there a way to do this without using an attribute on the model?

My model is separate from my implementation. It does not have a reference to the persistence implementation at all.

Comment by Robert Stam [ 30/Oct/10 ]

Added BsonDateTimeOptions attribute. Samples include:

[BsonDateTimeOptions(Kind = DateTimeKind.Utc)] // the default
[BsonDateTimeOptions(Kind = DateTimeKind.Local)]

// the following are for DateTime values intended to store only a Date value (so TimeOfDay component is zero)
[BsonDateTimeOptions(Kind = DateTimeKind.Utc, DateOnly = true)] // the default
[BsonDateTimeOptions(Kind = DateTimeKind.Local, DateOnly = true]

// the following specify that DateTime values should be stored as BsonType.String instead of BsonType.DateTime
[BsonDateTimeOptions(Representation = BsonType.String)]
[BsonDateTimeOptions(Representation = BsonType.String, DateOnly = true)]

Comment by Testo [ 28/Oct/10 ]

would be nice if I can decorate DateTime property field with an attribute to specify the DateTimeKind i need

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