How to MongoDB in C# – Part 2

MongoDB in C# - Ozcode
In the second part of this blog post, we will examine MongoDBs core ingredients.

“How to MongoDB in C#” is a two-part blog-posts:

  1. How to MongoDB in C# – Part 1
  2. 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#. In this post, 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 are 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 select the server to which specific commands and queries are sent, 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, but rather, by interacting with the Bson & Driver

Introduction to MongoDB’s client objects

For your application to communicate with MongoDB, you need to get familiar with the following objects:

MongoClient

This is a point-of-entry, thread-safe object. It 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 you can 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 a server other than the primary)
  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.
Enhance Visual Studio Debugging - Ozcode

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

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

So why is it important?
Because it allows you to listen to what happens “under the hood” in MongoDB (the core events):

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

Note:
TraceCommandsWith” is an extension method which registers TraceSourceCommandEventSubscriber to listen for all command-event types.
different subscribers

Let’s see how we can track the “find a single developer” request-response.
Fortunately MongoDB provides a pre-defined set of 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(%%EDITORCONTENT%%amp;amp;amp;amp;quot;************** 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(%%EDITORCONTENT%%amp;amp;amp;amp;quot;************** 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 the connection management 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 , though, that 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 this 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
Here, we can see “options”, and “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

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
Search C# collections in MongoDB - Ozcode

IMongoCollection also supports AsQueryable:

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

By extending C# IQueryableIMongoQueryable  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 first populated in a Developer instance and then 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 only be one (which is basically what we expect since we inserted only “John smith”).

Developer:

Looking for developers

Now, let’s zoom-in into Developer represented as a BsonDocument. The BsonDocument is, in fact, a key-value store which holds many 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

As 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 expression 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 that same method/indexer to offer. 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):

Bson Document

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!


Ozcode Visual Studio Extension

Elevate LINQ debugging with visibility, clarity and insights into your queries.

Time travel to see how your code will execute before you step through it.

Heads-up display gives you powerful visualizations so you can instantly understand what’s happening in your code.

Data tips provide deep insights into your data letting you drill down to any level in a data object.

Ozcode Visual Studio Extension - Free Download

 

Stas Rivkin

Comments

Keep your code in good shape!

Subscribe to our blog and follow the latest news in the .NET debugging industry

Ready to Dive into Your Prod Code?

Easy debugging with full time-travel data

Share on facebook
Share on twitter

Recent Posts

Follow Us

Join Ozcode YouTube Channel

Let’s start debugging, it’s free!

Ozcode Logo

This website uses cookies to ensure you get the best experience on our website.