Working with MongoDB in .NET (Part 1): Driver Basics & Inserting Documents
MongoDB, classified as a NoSQL database, is a document-oriented database system which stores data in JSON-like format. MongoDB represents JSON documents in a binary-encoded format called BSON behind the scenes, and the MongoDB BSON implementation is lightweight, fast, and highly traversable. This means that MongoDB gives users the ease of use and flexibility of JSON documents together with the speed and richness of a lightweight binary format.
In this tutorial series, I'll show you how to work with MongoDB in your .Net applications using the CRUD functions available from the .Net driver. MongoDB drivers allows you to work with MongoDB from different programming language. In this tutorial series, we'll be working with the C# driver.
Getting Started
To get started, fire up VisualStudio and create a new project. I'll be working with a console project for this tutorial. To install the driver, we'll go through NuGet and pull down the packages needed. There are three NuGet packages needed, and they are:
- MongoDB.Bson: The standalone BSON library that handles the conversion of POCOs to BSON types (which is the file format for MongoDB) and vice versa.
- [MongoDB.Driver.Core]((http://www.nuget.org/packages/mongodb.driver.core): This is a driver by itself and has the core components of the driver (like how to connect to a
mongod
instance, connection pooling, and the likes of it) for communicating from .Net to MongoDB and vice-versa, and has a dependency onMongoDB.Bson
. - MongoDB.Driver: Has a dependency on
Driver.Core
which in turn has a dependency onMongoDB.Bson
. It has easier to use API over the core component driver and has async methods and supports querying with LINQ.
Run the following command to get all three packages installed at once:
Install-Package MongoDB.Driver
Accessing a database
To connect to a database we use the MongoClient
class to access a mongodb instance and through it, select the database we want to use. This class has four constructors.
- A parameterless contructor which by default, connects to an instance on port 27017:
var client = new MongoClient();
- One that accepts a connection string:
var connectionString = "mongodb://localhost:27017";
var client = new MongoClient(connectionString);
- Another that takes an instance of
MongoUrl
, and MongoUrl being similar to using theconnectionstring
param constructor. You can create an instance of this by using the default constructor:
var client = new MongoClient(new MongoUrl("mongodb://localhost:27017"));
...or using the static Create
method from the class:
var client = new MongoClient(MongoUrl.Create("mongodb://localhost:27017"));
- And also, a constructor that accepts a
MongoClientSettings
instance. There are many things you can set in here, like the credentials, connections lifetime and timeout, and many more. An example of doing this would be:
var settings1 = MongoClientSettings
.FromUrl(MongoUrl.Create("mongodb://localhost:27017"));
var settings2 = new MongoClientSettings
{
Server = new MongoServerAddress("localhost", 27017),
UseSsl = false
};
var client1 = new MongoClient(settings1);
var client2 = new MongoClient(settings2);
Generally, you'd usually use the one constructor with the connectionString
parameter, and we'll use this for this tutorial. I'll add an async method within the Main
method because we'll be working with the async methods from the driver. If you're coding along, add the following code to your Program.cs
file:
using MongoDB.Driver;
using System;
using System.Threading.Tasks;
namespace WorkingWithMongoDB
{
class Program
{
static void Main(string[] args)
{
MainAsync().Wait();
Console.ReadLine();
}
static async Task MainAsync()
{
var connectionString = "mongodb://localhost:27017";
var client = new MongoClient(connectionString);
}
}
}
Now run the application and see that it successfully connects to the mongodb instance on that port; and from the console, you can see how many connections are open.
With MongoClient instance, there are a couple of things we can do like drop a database, get a database, or retrieve a the names of databases on the server. There is none for creating a database because once you pick a database and insert data into it, it automatically creates the database.
The one we're interested in is the GetDatabase
method which will automatically create a database for us. So let's go ahead and fetch a database called school
for which we'll work with:
IMongoDatabase db = client.GetDatabase("school");
The GetDatabase method returns an object which is a representation of a database, from which we can access different collections and manipulate the database. The MongoClient
object is thread safe, so you can put it in a static field, make it a Singleton which you can get anytime through your DI container, or instantiate a new one using the same connection settings (which underneath will use the same connection pool); and through this object, you can pick any database you want to work with. I personally do have it as a Singleton registered to my chosen DI container.
With the database object, you can create, rename, retrieve, or get a list of collections from the database. Documents are stored in collections, so you can think of a collection as a table and documents as records in a table, if you're coming from the SQL world.
Create a collection
To create a collection, we use the CreateCollection or CreateCollectionAsync method of the IMongoDatabase
object. This method takes in three parameters (of which the last two are optional):
- The name of the collection
- Create collection options
- A cancellation token:
void CreateCollection(
string name,
CreateCollectionOptions options = null,
CancellationToken cancellationToken = null
)
Task CreateCollectionAsync(
string name,
CreateCollectionOptions options = null,
CancellationToken cancellationToken = null
)
The CreateCollectionOptions specify settings for a collection, e.g. the maximum number of documents it should contain. Here's an example:
await db.CreateCollectionAsync("students", new CreateCollectionOptions
{
AutoIndexId = false,
MaxDocuments = 25,
Capped = true
});
Most of the time, we just want to create a collection and leave the options at its default, by setting just the name
of the collection.
await db.CreateCollectionAsync("students");
Another way a collection can also be created is using the GetCollection which accepts a name for the collection and an option collection settings as parameters. With this method, even though a collection of that name doesn't exist, it'll go ahead and create that collection once a document is being created. This would typically be the way you want to go, and would only use the create variant when you need to create a capped collection.
A capped collection is a fixed-sized collection that automatically overwrites its oldest entries when it reaches its maximum size. The GetCollection method is generic and you need to specify a document type when calling this method. The type represents the kind of object/document we want to work with. This could be strongly typed to any class we define, or use the BsonDocument
type to represents a dynamic schema allowing us to work with any document shape in the collection.
Getting a collection
Having talked about creating a collection, it'll require an extra step to check if a collection exists, create it, and then add documents to a collection. The GetCollection
automatically creates a collection if none exist and adds documents to that collection. Therefore, even though there's a CreateCollection
, we'd typially want to go down this route. Just like the database, the collection is also thread safe and very cheap to create. To get a collection, we call the GetCollection
method specifying the document type
static async Task MainAsync()
{
......
IMongoCollection<BsonDocument> collection = db.GetCollection<BsonDocument>("students");
}
The BsonDocument is a type from the MongoDB.Bson package which represent a BSON Document and with this type, we can work with any shape of data from the database. This package contains all the basic BSON types and a few other things for working with BSON.
Within this package, we have classes that represent BSON types and how to map between .NET types and BsonValues. A few of those are:
- The BsonDocument type as we've discussed
- BsonElement which represent a BSON element
- BsonValue which is an abstract base class used by various subclasses like the BsonString, BsonInt32, and many more.
The BsonDocument
is a dictionary of string to BSON value, so we can initialize as we would any dictionary:
var document = new BsonDocument
{
{"firstname", BsonValue.Create("Peter")},
{"lastname", new BsonString("Mbanugo")},
{ "subjects", new BsonArray(new[] {"English", "Mathematics", "Physics"}) },
{ "class", "JSS 3" },
{ "age", int.MaxValue }
};
...or use the Add
method which has a number of overloads:
var document = new BsonDocument();
document.Add("name", "Steven Johnson");
document.Add("age", 23);
document.Add("subjects", new BsonArray() {"English", "Mathematics", "Physics"});
...or use an indexer:
document["class"] = "JSS 3";
Creating/Inserting a document
Documents are stored within a collection and having looked at creating and getting a collection, we'll move on to inserting new documents in a collection. The mongo collection instance provides methods to insert a single document at a time or multiple documents at once.
To do this, we have to:
- Get a hold of an object of type
IMongocollection
which represents the collection we want to work with:
var collection = db.GetCollection<BsonDocument>("students");
- And then create the document we want:
var document = new BsonDocument
{
{"firstname", BsonValue.Create("Peter")},
{"lastname", new BsonString("Mbanugo")},
{ "subjects", new BsonArray(new[] {"English", "Mathematics", "Physics"}) },
{ "class", "JSS 3" },
{ "age", 45}
};
- And finally insert the document:
await collection.InsertOneAsync(document);
To see this working, let's start a mongod
instance from the command line and run the following lines of code while monitoring the events from the console:
class Program
{
static void Main(string[] args)
{
MainAsync().Wait();
Console.ReadLine();
}
static async Task MainAsync()
{
var client = new MongoClient();
IMongoDatabase db = client.GetDatabase("school");
var collection = db.GetCollection<BsonDocument>("students");
var document = new BsonDocument
{
{"firstname", BsonValue.Create("Peter")},
{"lastname", new BsonString("Mbanugo")},
{ "subjects", new BsonArray(new[] {"English", "Mathematics", "Physics"}) },
{ "class", "JSS 3" },
{ "age", 23 }
};
await collection.InsertOneAsync(document);
}
}
... starting a mongod instance:
Run the application and watch the console:
You will notice that it called the insert command and succesfully inserted one document (ninserted: 1
) . Also, there is a synchronous version of this method:
collection.InsertOne(document);
We can also insert multiple documents at the same time using the InsertMany
or InsertManyAsync
methods. Assuming we have three new students in the school, we can insert all at the same time using this method and they will be inserted in one batch (assuming you're using MongoDB 2.6 or higher). To see this in action, we move on to updating our code base and running the application:
class Program
{
static void Main(string[] args)
{
MainAsync().Wait();
Console.WriteLine("Press enter to exit");
Console.ReadLine();
}
static async Task MainAsync()
{
var client = new MongoClient();
IMongoDatabase db = client.GetDatabase("schoool");
var collection = db.GetCollection<BsonDocument>("students");
var newStudents = CreateNewStudents();
await collection.InsertManyAsync(newStudents);
}
private static IEnumerable<BsonDocument> CreateNewStudents()
{
var student1 = new BsonDocument
{
{"firstname", "Ugo"},
{"lastname", "Damian"},
{"subjects", new BsonArray {"English", "Mathematics", "Physics", "Biology"}},
{"class", "JSS 3"},
{"age", 23}
};
var student2 = new BsonDocument
{
{"firstname", "Julie"},
{"lastname", "Lerman"},
{"subjects", new BsonArray {"English", "Mathematics", "Spanish"}},
{"class", "JSS 3"},
{"age", 23}
};
var student3 = new BsonDocument
{
{"firstname", "Julie"},
{"lastname", "Lerman"},
{"subjects", new BsonArray {"English", "Mathematics", "Physics", "Chemistry"}},
{"class", "JSS 1"},
{"age", 25}
};
var newStudents = new List<BsonDocument>();
newStudents.Add(student1);
newStudents.Add(student2);
newStudents.Add(student3);
return newStudents;
}
}
From the console, you can see that it issued an insert command for three documents and successfully inserted all of them. Aside from working with the BsonDocument
, we usually know beforehand what kind of data we want to work with and we can create custom .Net classes for them. Following our example of working with the students
collection, let's create a Student
class and insert new students represented using this class:
internal class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Class { get; set; }
public int Age { get; set; }
public IEnumerable<string> Subjects { get; set; }
}
class Program
{
static void Main(string[] args)
{
MainAsync().Wait();
Console.WriteLine("Press enter to exit");
Console.ReadLine();
}
static async Task MainAsync()
{
var client = new MongoClient();
IMongoDatabase db = client.GetDatabase("schoool");
var collection = db.GetCollection<Student>("students");
var newStudents = CreateNewStudents();
await collection.InsertManyAsync(newStudents);
}
private static IEnumerable<Student> CreateNewStudents()
{
var student1 = new Student
{
FirstName= "Gregor",
LastName= "Felix",
Subjects = new List<string>() {"English", "Mathematics", "Physics", "Biology"},
Class = "JSS 3",
Age = 23
};
var student2 = new Student
{
FirstName = "Machiko",
LastName = "Elkberg",
Subjects = new List<string> {"English", "Mathematics", "Spanish"},
Class = "JSS 3",
Age = 23
};
var student3 = new Student
{
FirstName = "Julie",
LastName = "Sandal",
Subjects = new List<string> {"English", "Mathematics", "Physics", "Chemistry"},
Class = "JSS 1",
Age = 25
};
var newStudents = new List<Student> {student1, student2, student3};
return newStudents;
}
}
With the code above we can change the document type for the collection to the new class, and call the insert method. To see that this works, let's run the application and monitor what happens from the console.
Wrapping up
And from that, we can see that it actually does insert the documents. Having looked at all this, I certainly hope that some basics of the .Net driver is clear and we know how to insert document(s). In the next part, we'll walk through retrieving documents and the various ways to build queries for that purpose.
Nice article; I’d love to hear peoples experiences with scaling Mongo deployments. During some initial load testing I’ve found that using the InsertOneAsync method became fairly unreliable once I ramped up 50 concurrent operations…I started to receive MongoWaitQueueFullException. Replacing this with the synchronous InsertOne version fixed the problem to the point that I could not get it to fail no matter how hard I tried…however, I am guessing this have knock-on effects to the server hosting the components that connect to mongo. Any thoughts\comments on this scenario?
Thank you! Excellent work!
Hi , i am new to mongodb , currently i am struggling to connect mongodb (C#) with credential and disconnect database, can you please help me to solve this?
what errors are you getting?
Hi Peter,
now i’m trying to disconnect the database connection using below code
MongoDB.Driver.MongoDatabase _database = (MongoDB.Driver.MongoDatabase)meta.MongoConnection;
After executing the above code i didn’t see any errors, but still the state shows connected.