How to MongoDB in C# – Part 2

Recap on MongoDB for .NET

In the previous post, we mainly talked about MongoDB’s object-mapping inside C#. We will continue our introduction, but this time with MongoDB’s core ingredients.

When you add MongoDB to your project using NuGet, Three new references will be added to your project:

MongoDB nuget

Let’s explore them:

  • MongoDB.Bson contains the code for the Bson object model – BsonDocument, BsonValue etc. as well as Mapping from POCO to Bson and vice-verse.
  • MongoDB.Driver.Core contains all the server related connectivity – how to talk to the server, how to make sure to which server a specific commands and queries will be sent to, connection-pool etc.
  • MongoDB.Driver a lightweight syntax wrapper on top of MongoDB.Driver.Core

Note that usually you’ll never call the Core library directly, only through interacting with the Bson & Driver

Introduction to MongoDB’s client objects

In order for your application to communicate with MongoDB, we need to get familiarize with the following objects:

MongoClient

Point of entry, thread-safe object. May be saved in a static variable or even inside an IOC container.

Also, this class can be safely created and re-created multiple times as long as you use the same connection settings (MongoDB will use the same connection-pool under the hood).
There are several ways for you to pick on how to initialize a MongoDB client:


// Empty ctor will get you a 
// client with a default localhost and port #27017
MongoClient = new MongoClient();
----------------------------------------------------------------------
// Using a connection-string
MongoClient = new MongoClient("mongodb://localhost:27017");
----------------------------------------------------------------------
// Using MongoClientSettings
MongoClient client = new MongoClient(
 new MongoClientSettings
 {
   Server = new MongoServerAddress("localhost",27018),
   // Giving 3 seconds for a MongoDB server to be up before we throw
   ServerSelectionTimeout = TimeSpan.FromSeconds(3)
 });
----------------------------------------------------------------------
// Using MongoUrl
MongoClient client = new MongoClient(
 MongoUrl.Create("mongodb://localhost:27017"));
Note:
By default, MongoDB will connect to port 27017 on your localhost.

**MongoClientSettings, contains:

  1. Servers
  2. Timeouts
  3. Credentials
  4. Pool sizes
  5. Read preferences (when a read is required from other then the primary server)
  6. SSL settings
  7. Write concern – what durability you would like your write to persist to
  8. ClusterConfigurator
Note:
There are many properties you can set here, but MOST of them are fine as they are.

ClusterConfigurator is an important option, as it allows you to change different configuration (hence the name). It requests an Action delegate which accepts ClusterBuilder


private MongoClient CreateClient()
{
  return new MongoClient(
    new MongoClientSettings
    {
      ClusterConfigurator = builder =>  {  }
    });
}

Than, why is it important?
It allows you to listen to what happens “under the hood” in MongoDB (the core events):
Builder API

Take “Subscribe” method for example, it accepts any type as long as it implements IEventSubscriber, therefore enables you to hook to MongoDB events.

Note: 
"TraceCommandsWith" is an extension method, 
which registers TraceSourceCommandEventSubscriber to listen for all command-event types

 

Let’s see how we can track the “find a single developer” request-response.
Fortunately MongoDB provides pre-defined set of subscribers:
different subscribers

We are going to use SingleEventSubscriber to track a single request and a single response.

private void WithCoreEventsSubscription(Developer developer)
{
  MongoClient client = CreateClient();
  IMongoDatabase db = client.GetDatabase("csharp");

  IMongoCollection<Developer> devCollection = db.GetCollection<Developer>("developers");
  devCollection.InsertOne(developer);
  
  //Here our "CmdStartHandlerForFindCommand" will be triggered for Find request 
  //and as response our "CmdSuccessHandlerForFindCommand" will be triggered as well      
  developer = devCollection.Find(FilterDefinition<Developer>.Empty).Single();
}

//This is the place where we would setup our listeners using the "builder"
private MongoClient CreateClient()
{
 return new MongoClient(
  new MongoClientSettings
  {
    ClusterConfigurator = builder =>
    {
      builder.Subscribe(new SingleEventSubscriber<CommandStartedEvent>(CmdStartHandlerForFindCommand));
      builder.Subscribe(new SingleEventSubscriber<CommandSucceededEvent>(CmdSuccessHandlerForFindCommand));
    }
  });
}

private void CmdStartHandlerForFindCommand(CommandStartedEvent cmdStart)
{
  if (cmdStart.CommandName == "find")
  {
    WriteToConsole(cmdStart.Command, "request");
  }
} 

private void CmdSuccessHandlerForFindCommand(CommandSucceededEvent cmdSuccess)
{
  if (cmdSuccess.CommandName == "find")
  {
    WriteToConsole(cmdSuccess.Reply, "response");
  }
} 

//We can use out-of-the-box ToJson extension method provided
//by MongoDB, which transforms BsonDocument to a readable JSON
private void WriteToConsole(BsonDocument data, string type)
{
  Console.WriteLine($"************** Find {type} **************");
  Console.WriteLine(data.ToJson(
    new JsonWriterSettings
    {
      Indent = true
    }));
}

subscription results

Or we can use ReflectionEventSubscriber by specifying which events we are interested in internally

//Subscribe method revisited
private MongoClient CreateClient()
{
 return new MongoClient(
  new MongoClientSettings
  {
    ClusterConfigurator = builder =>
    {
      builder.Subscribe(new MySimplifiedSubscriber());
    }
  });
}

private class MySimplifiedSubscriber : IEventSubscriber
{
 private readonly ReflectionEventSubscriber _subscriber;
 public MySimplifiedSubscriber()
 {
   //You can pass any object to "ReflectionEventSubscriber"
   //and can specify the binding flag to search up the method
   //I've choose private methods for this example
   _subscriber = new ReflectionEventSubscriber(this,
                    bindingFlags:BindingFlags.Instance|BindingFlags.NonPublic);
 }

 public bool TryGetEventHandler<TEvent>(out Action<TEvent> handler)
 {
   return _subscriber.TryGetEventHandler(out handler);
 }

 private void Handle(CommandStartedEvent cmdStart)
 {
   if (cmdStart.CommandName == "find")
   {
     WriteToConsole(cmdStart.Command, "request");
   }
 }

 private void Handle(CommandSucceededEvent cmdSuccess)
 {
   if (cmdSuccess.CommandName == "find")
   {
     WriteToConsole(cmdSuccess.Reply, "response");
   }
 }

 private void WriteToConsole(BsonDocument data, string type)
 {
   Console.WriteLine($"************** Find {type} **************");
   Console.WriteLine(data.ToJson(
            new JsonWriterSettings
            {
              Indent = true
            }));
 }
}

 

You can check which events are available in the MongoDB.Driver.Core.Events namespace and for a full list of MongoDB Commands.

By the way, as a developer you won’t have to deal with connection management related stuff. MongoDB handles all of the connection managements for you (Closing, disposing and release of connections back to the pool for later re-use)

IMongoDatabase

A new instance implementing IMongoDatabase can be acquired by calling MongoClient‘s “GetDatabase” method:

var client = new MongoClient();
IMongoDatabase db = client.GetDatabase("csharp");

Well.. the actual method interface is:
settings for db
As you can see, there is an optional parameter of MongoDatabaseSettings, interesting enough MongoDB lets you to override some of the settings you defined on the MongoClient and also pass MongoCollectionSettings to further override Client and/or Database settings when creating your IMongoCollection (we will address it later on)

IMongoDatabase, is responsible for all collection related CRUD operations and some more, such as (Most of those CRUD operations have an async support as well):

  • Create a collection
  • Drop a collection
  • Rename a collection name
  • Get specific collection
  • Lists your collection – provides a metadata information for each of your collections in a specific IMongoDatabase instance, you can use it to log it during debug time or whatever comes to mind.

collection metadata
we can see here “options”, “indexes” among other trivial things

  • options refer to MongoXXXSetttings passed at creation
  • idIndex refer to different indexes applied on this collections (we can see the default index – key “_id” of which we discussed in the previous post)
  • “ns : csharp.developers” –  the “csharp” is not related to the language I’m writing in, it just how I named the database where the “developers” collection resides in.

IMongoCollection<TDocument>

The IMongoCollection is your gateway to all of MongoDB’s goodies, using it you will gain access to:

  1. Indexes API
  2. Aggregation API
  3. CRUD API

IMongoCollection also supports AsQueryable:

IMongoCollection<TDocument> dbCollection = db.GetCollection<TDocument>("generic");
IMongoQueryable<TDocument> queryDbCollection = dbCollection.AsQueryable();

By extending C# IQueryable<T>IMongoQueryable<TDocuement>  adds MongoDB specific methods and giving it an async support for the LINQ API.

Note: 
Deleting your collection during runtime and re-accessing it, won't throw any exception.
Next time you try to approach your collection with a query of some sort, 
MongoDB will automatically re-create the collection and run your query against it.

So.. Why use TDocument?

as I mentioned in the previous post everything is a Document or to be more precise everything is a BsonDocument when using the C# API. However, you don’t actually have to create a strongly typed class to populate it with values returning from MongoDB. For example, consider the Developer class we introduced in the previous post.

In the example below, we will look at the same data once populated in a Developer instance and in the second time inside MongoDB’s base class, BsonDocument.

Don’t wrap your head around the query syntax, we will get to it.. eventually. We are looking for all developers and telling MongoDB there can be only one (which is basically what we expect since we inserted only “John smith”)

Developer:simple developer

Now, let’s zoom-in into Developer represented as a BsonDocument. The BsonDocument, is in fact a key-value storage which holds a lot of properties.

Personally, I find it hard to believe you will ever use all of it’s properties in your code.

I have grouped most of the meaningful properties using OzCode‘s Reveal feature. Which enables me to focus on the properties and values I care about during debugging:
BsonDocument RAW

You can see, it can be very cumbersome to scan the BsonDocument for values (it depends on the depth of your JSON object).

Instead, I waved OzCode‘s magic wand magic wand and created a new Custom expressions to help me better understand my data:

custom expression usage

The way it works, you can add any method/indexer call on the instance as long as the class has to offer that same method/indexer. Once I’ve created a new custom expression, OzCode will remember it even after the end of this specific debug session, so the next time you hover over an instance of type X, the new “Properties” you have just added will be there.

BsonDocument (re-examined):
BsonDocument - custom expressions

This time I have gathered all of the data (again) I’m interested in the BsonDocument and created new “properties” at Debug-Time. Making it more easy for me to read the data.

I can even create my very own “custom expressions”:
custom expressions

What’s next

Up until now we’ve covered the top-most basics of:

  1. Relationship between JSON object and it’s twin C# brother, the Document, Object-Mapping, ID property (previous post)
  2. MongoClient
  3. MongoDatabase
  4. MongoCollection

 

Finally, we are getting to the real juicy stuff:

  • Analyzing CRUD operations results using OzCode’s Search, Collection Filter and Export features
  • Aggregation FW from C# perspective – simple and complex as one

Stay tuned!

Stas Rivkin

Software consultant @CodeValue & a @OzCode evangelist. He has been writing software professionally for about 5 years, mostly in C# and .Net. Considers Clean Code as a form of an art.

  • Alex

    Awesome post! Thank you!

  • Paweł Kunicki

    Hi, When you will release 3rd part 😉 ?

    • Stas Rivkin

      Work in progress 🙂

  • Squiggley Woo

    Great article, I have been performance testing my application and its turning out that mongodb is a bottleneck. So I am trying to play around with MongoClientSettings, like increasing the SocketTimeout or the WaitQueueSize etc but they are always frozen. I cannot find any real documentation on it.

    I want to be able to be running my app and tweak these settings at runtime. Is this possible? and if so how would I go about it?

    Thanks