Persisting and fetching DateTime values with MongoDB

28 03 2012

The C# driver for MongoDB serializes data by default from C# objects to a bson representation which is stored inside MongoDB. The DateTime type is a bit special which I want to demonstrate with the following test.


public abstract class RepositorySubjectwhere T : Repository
{
    public static T Subject { get; set; }

    public RepositorySubject()
    {
        var mongoDb = new MongoDB();
        Subject = (T)Activator.CreateInstance(typeof(T), mongoDb);
    }
}


public static class BlogRepositorySpecs
{
    [Subject(typeof(BlogRepository))]
    public class When_refetching_persisted_data : RepositorySubject
    {
        private static BlogPost blogEntry;

        private static BlogPost result;

        Establish context = () =>
        {
            blogEntry = new BlogPost()
            {
                Author = "Test Author",
                Comment = "My Comment",
                CreationDate = new DateTime(2012, 4, 12),
                Id = ObjectId.GenerateNewId()
            };
        };

        Because of = () =>
        {
            Subject.Save(blogEntry);
            result = Subject.FindById(blogEntry.Id.ToString());
        };

        Cleanup after = () => Subject.Drop();

        It should_have_the_correct_creationdate = () => {
           result.CreationDate.ShouldEqual(blogEntry.CreationDate);
        }

        It should_have_the_correct_author = () => {
            result.Author.ShouldEqual(blogEntry.Author);
        }

        It should_have_the_correct_comment = () => {
            result.Comment.ShouldEqual(blogEntry.Comment);
        }
    }
}

This simple test is written with mspec. What the tests does is create a BlogPost-object with a DateTime value and 2 string values. This object is persisted inside MongoDB. Afterwards the object is retrieved and the expected data is compared to the retrieved data.
The abstract RepositorySubject is a helper which can be used for different mspec tests which are written to test different repository functionality.
The Repository class by itself is an abstract class holding the most important CRUD-operations for the usage with MongoDB. BlogRepository is inherited from Repository. Additional implementations can be added here. Inside the MongoDB class the connection to the MongoDB database is established and the commands are executed.

Running the Test

When we run the test on a machine where the current time is set to something else than UTC time the test will fail. Why will the test fail? DateTime values are stored as UTC time inside the database. When we retrieve the data from the database the DateTime value is fetched as a UTC DateTime value. Therefore we compare a local DateTime with a UTC DateTime value.

Set Serialization Options for DateTimes

What can we do to fix this problem? We can register a serialization option for the DateTime value. With this option we can specify that the value will be converted to the local DateTime when we retrieve the object from the database. The following code snippet shows how to transfer the CreationDate from UTC to the local DateTime value.


public class RegisterSerializer
{
    public void Setup()
    {
        BsonClassMap.RegisterClassMap(cm => {
            cm.AutoMap();
            cm.GetMemberMap(c => c.CreationDate).SetSerializationOptions(
                    new DateTimeSerializationOptions(DateTimeKind.Local));
        });
     }
 }

After adding this class we only need to call the method which can be done inside the RepositorySubject by adding the following 2 lines of code.


var registerSerializer = new RegisterSerializer();
registerSerializer.Setup();

Now we can run the tests again. The result should be as expected – green.

Advertisements




Query and update data on MongoDB using PowerShell

30 01 2012

I’m working on client side at the moment where we use MongoDB as data storage. We reached the point where we need to read some data from MongoDB with PowerShell. I thought this might be interesting to some people. Therefore I decided to share my experience about this.

What do we need to connect to MongoDB with PowerShell

The only thing we need is a MongoDB driver. I used the official C# driver which is supported by 10gen. 10gen is the company which develops MongoDB and offers support, training and consulting for MongoDB. The latest driver binaries can be downloaded at github from https://github.com/mongodb/mongo-csharp-driver/downloads

How to connect to the MongoDB server

First you need to add references to the dll’s which are provided by the MongoDB driver. Then you can establish the connection to the database. The next step is to connect to a collection. On the collection you can perform different CRUD operations.

$mongoDbDriverPath = "C:\driver\mongoDB\"
$dbName = "MyDatabase"
$collectionName = "MyCollection"
Add-Type -Path "$($mongoDbDriverPath)\MongoDB.Bson.dll"
Add-Type -Path "$($mongoDbDriverPath)\MongoDB.Driver.dll"
$db = [MongoDB.Driver.MongoDatabase]::Create("mongodb://localhost/$($dbName)")
$collection = $db[$collectionName]

Create a new document

First you need to create a BsonDocument. On the document you can add new key-value pairs by calling the add-method. As soon you finalized the new document you can store the document inside a collection with the save-method.

$document = new-object MongoDB.Bson.BsonDocument
$document.Add("PreName",[MongoDB.Bson.BsonValue]::Create("Daniel"))
$document.Add("LastName",[MongoDB.Bson.BsonValue]::Create("Weber"))
$collection.save($document)

When you use the add-method like on the sample above, MongoDB automatically creates a string value for you. If you want to create an entry with an e.g. an ObjectId, you can use the following statement.

$document.Add("UserId",[MongoDB.Bson.BsonObjectId]::Create("4f2193e0df6e251d040a6df6"))

For other object types please have a look at the MongoDB driver documentation.

Read data from collection

If you want to fetch all data from a single collection and display all objects you can use the following command.

$results = $collection.findall()
foreach ($result in $results) {
    write-host $result
}

For the reason you are only interested in a single column you can access a single columns with the following notation.

foreach ($result in $results) {
    write-host $result[“LastName”]
}

If you want to select a specific document you need to start writing a query. If you want to select all documents which have a specific LastName use the following query.

$query = [MongoDB.Driver.Builders.Query]::EQ("LastName","Weber")

For more complex scenarios you can combine different conditions to a single query. If you want to select all documents having the specified LastName and PreName use this command.


$query = [MongoDB.Driver.Builders.Query]::AND(
            [MongoDB.Driver.Builders.Query]::EQ("PreName","Daniel"),
            [MongoDB.Driver.Builders.Query]::EQ("LastName","Weber"))

The driver offer many different options to combine conditions to a single query. Not only an AND exists. After building the query, you need to find the results. This can be done by calling a find with the query as parameter.

$results = $collection.find($query)

Update data inside a collection

To update an existing document you can start with querying for an existing document. The following command search for a document with the provided ObjectId. The Id on the document is unique. Therefore we can use findOne to get this document from the collection (because we expect only a single result). To update the fetched document we use the set-method. When everything is changed don’t forget to call save. The fact that we have a document with an existing ObjectId forces MongoDB to update the document and not to create a new one.

$query = [MongoDB.Driver.Builders.Query]::EQ("_id",
            [MongoDB.Bson.BsonObjectId]::Create("4f2198f8df6e251d040a6e17"))
$result = $collection.findOne($query)
$result.Set("LastName",[MongoDB.Bson.BsonValue]::Create(“W.”))
$collection.save($result)

Delete documents from a collection

Deleting existing documents from a collection is easy. If you want to delete every document you can do this by calling removeAll. This will remove every document from the specified collection. Most of the time, you only want so delete a single document. To remove only specific data call the remove-method on the collection with a query. Every document which is returned by the query will be then be deleted.


$query = [MongoDB.Driver.Builders.Query]::EQ("PreName",
            [MongoDB.Bson.BsonValue]::Create("Daniel"))
$collection.remove($query);

Conclusion

With the C# driver it is easy to perform CRUD-Operations with MongoDB. Just have a look at the documentation of the driver if you need to perform some advanced queries. I really like how easy it is to connect to MongoDB via PowerShell.