Database

MongoDB with C#

In this console application, I'll demonstrate two possible mechanisms to retrieve data from the retrogames MongoDB database. One of the possibilities consists of creating your own C# classes to represent the documents in the solution and perform the necessary mapping tweaks. So when you read a document from the database, you get back a C# object, known as a POCO (short for "Plain Old C# Object") instance. The driver transforms the BSON document into the POCO instance through deserialization. When you create a new POCO instance to add a new document to the database, the driver transforms the POCO instance you created to a BSON document through serialization. (When you chose to go this route, you might have some issues with the underlying schema flexibility. There is another alternative that preserves the schema flexibility and doesn't require your own domain classes by working with plain BsonDocument instances,I'll dive into that option later.)

For this example, I'll focus on creating the domain classes. First, add a new interface, IMongoEntity:

The IMongoEntity interface defines an Id field of the MongoDB.Bson.ObjectId type that represents a BSON ObjectId. Next, add a new class, MongoEntity, that implements the previously created IMongoEntity interface:

The MongoEntity class uses the MongoDB.Bson.Serialization.Attributes.BsonIdAttribute (BsonId) attribute to specify that the Id field must be mapped to the _id field for each document during serialization and deserialization.

If you take a look at the previously explained Text View for the games collection in MongoVUE, you will be able to determine the fields required in a C# class to represent a game document. The following lines show the JSON text of the existing document as shown in the Text View:

The Game class uses the MongoDB.Bson.Serialization.Attributes.BsonElementAttribute (BsonElement) attribute to specify the BSON document's field name, which has to be mapped to the property. Thus, the underlying serialization process knows that the value for release_date is mapped to the ReleaseDate property. In this case, I've used different names and cases in the class; therefore, it was necessary to annotate it with attributes to configure automatic serialization. However, if you use the same names in the document's fields and in the class, you won't need to add annotations.

There are other attributes that allow you to override the automatic serialization behavior for the different types. For example, you can force the BSON representation of a .NET type to BSON Int64 by using the following annotation:

[BsonRepresentation(BsonType.Int64)]

When you use annotations, your domain classes are not independent of the persistent layer. To avoid that situation, it is also possible to configure serialization in code instead of using attributes. For example, the following lines are the equivalent serialization configuration of the previously shown attributes for the Game class that use the methods of the MongoDB.Bson.Serialization.BsonClassMap class:

The following lines show the code for Program.cs, which uses the FindOne method to retrieve the first document in the games collection that includes a name field with the value equal to "Invaders 2013." I haven't included all the necessary exception handling code in order to keep the example as simple as possible. The driver transforms the BSON document to an instance of the Game class through deserialization (taking into account the annotations), as shown in Figure 10:

namespace RetrogamesConsole
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Builders;
using MongoDB.Driver.GridFS;
using MongoDB.Driver.Linq;
class Program
{
public static void Main(string[] args)
{
//// Warning: The following code is an example without the appropriate
//// Exception handling
var connectionString = "mongodb://localhost";
//// Get a thread-safe client object by using a connection string
var mongoClient = new MongoClient(connectionString);
//// Get a reference to a server object from the Mongo client object
var mongoServer = mongoClient.GetServer();
//// Get a reference to the "retrogames" database object from the Mongo server object
var databaseName = "retrogames";
var db = mongoServer.GetDatabase(databaseName);
//// Get a reference to the "games" collection object from the Mongo database object
var games = db.GetCollection<Game>("games");
var gameQuery = Query<Game>.EQ(g => g.Name, "Invaders 2013");
var foundGame = games.FindOne(gameQuery);
}
}
}

Understanding the Different Query Builder Alternatives

The previously shown code is equivalent to the following JavaScript commands in the MongoDB shell:

use retrogames
db.games.findOne({ name: "Invaders 2013"})

The code gets a thread-safe client object with a simple connection string that doesn't specify a port because the server is running in localhost using the default port: 27017. It isn't necessary to call any methods to either connect or disconnect to MongoDB because the driver automatically manages a connection pool. The code simply gets a reference to the MongoDB server object and uses it to get a reference to the retrogames database.

The following three lines get a reference to the games collection object, use the typed query builder (Query<Game>) to build a query that the driver will translate to an equivalent MongoDB query at runtime, and finally call the FindOne method with the typed query (gameQuery) as a parameter:

The typed query builder is both type-aware and type safe; therefore, it allows you to use the properties defined in your domain classes to build the query. It is also possible to use the untyped query builder, but it requires you to work with the underlying field names. For example, the following lines use the untyped query builder to generate the same results as the previously shown lines:

The methods provided by the untyped query builder require an element name; so if you provide the wrong element name, the query's execution will fail. Coding in the untyped query builder is definitely error-prone.

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task.
However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Video

This month's Dr. Dobb's Journal

This month,
Dr. Dobb's Journal is devoted to mobile programming. We introduce you to Apple's new Swift programming language, discuss the perils of being the third-most-popular mobile platform, revisit SQLite on Android
, and much more!