MongoDB is one of the most popular NoSQL databases out there, and for good reasons. For once, it does not have some of the limitations of more recent ones – I’m thinking of DocumentDB and Azure Table Storage, I really don’t know about the others –, is easy to get started with, and yet supports advanced scenarios, such as clustering and replication. Of course, it is also free and has an open source C# API!
In this new series of posts, I will talk about how to perform common MongoDB operations with .NET. This won’t be a full blown tutorial, just a couple of “recipes”, if you like. The first post – this one – will cover what we might call DML operations – inserting, modifying, deleting and querying data. I will use LINQ for all querying, since it is the natural way to do queries in the .NET world.
You will need the following packages from Nuget, they are the official .NET packages from MongoDB.
I know very well that there are others, but I decided to stick with the official ones.
For all the following examples, I will assume that I have the following object:
This will establish a connection to a MongoDB instance running on the local server. If you need to connect to another one, you need to pass a URL with the following format:
Mind you, the URL can be more complex, because it can take additional hosts as fallbacks, but let’s keep it simple for now. The username, password and port fields are of course optional. In the very minimum, if you want to connect to another server, this is what you’ll need:
The basic level of organization in MongoDB is the database. You always work in the context of a database. It’s the database that has the collections, functions and users (maybe another post). Documents, on the other hand, are stored in collections. Since collections are schemaless, you can store whatever you want in a collection, even totally unrelated and different documents.
A document in MongoDB is just some JSON content, or, more accurately, BSON. It can be represented in .NET by either a POCO class or by the BsonDocument class. BsonDocument allows programmatic access to its members, something that may come in handy. We can convert to and from using this code:
Setting a field is equally easy, the only thing is, you lose the strong typing:
The first operation that you are likely to encounter is inserts. Like I said, in MongoDB, you can either work with strongly typed .NET classes (aka, POCOs) or with BsonDocument instances. Here’s how to go, first, with a POCO class:
If instead you have a BsonDocument, it’s the same:
Finally, you don’t even need a pre-defined document class, you can build one on the fly with an anonymous type or a dictionary, and you convert it into a BsonDocument:
Another aspect to consider is the identifier. MongoDB always adds an _id property to all documents, where it stores its identifier. You have the choice to either supply your own value, or let MongoDB choose one for you. If you defined a class for your documents, you can have one property to map the _id property. You can use basic types for the identifier, like strings and numbers, but if you want MongoDB to take the responsibility to generate the identifier for you, you need to use the MongoDB type ObjectId:
I won’t cover conventions and mappings here, but you can use any other name for the identifier property, as long as you decorate it with the BsonIdAttribute attribute:
If you do not explicitly set a value to the identifier property, then MongoDB will provide you one, but you can certainly supply your own. The ObjectId structure can parse a string or accept a couple of other formats, and if you don’t use ObjectId, you can use whatever value you like, for example, strings and integers. After a successful insertion, you can get the generated identifier by inspecting the _id property of the BsonDocument (yes, you will need to convert the document to insert into a BsonDocument in order to retrieve the identifier, and no, unlike an ORM, the POCO class won’t be updated with the generated id):
This example was for inserting a single document, but you can insert several at the same time:
When it comes to modifying existing document, you have two options:
Let’s first cover updating parts of a document – mind you, this could even be all of the document’s properties minus the id (you cannot change an existing document’s identifier). Here is how it goes, for updating a single document; we need to set a filter and the updates that we want to do:
See how I add modifications to a builder, in this example, I am just setting a new value for the Date property.
This is the strongly typed version, using POCOs, but you can also do with BsonDocument:
Like I previously shown, a dictionary of key-values can be converted to a BsonDocument, and the same goes to an anonymous type.
You can do several modifications at the same time. For that, you just add modification items to the UpdateDefinitionBuilder<T> object:
All of these will be translated to native MongoDB update expressions. At the moment, it is not possible to update fields based on other fields, just some basic operations:
As for changing several documents at once, easy easy:
Just make sure that you do not supply an _id property with a value other than the current one!
When you replace a document you lose all of the existing properties, minus, of course, the id. You don’t need to follow the previous schema, you can use totally different contents:
Again, the replacement document cannot have a value for the identifier property that is different from the one that we are trying to update. It is OK to not include one at all.
It is not possible to replace many documents in a single shot.
For querying a strongly typed collection, we can use LINQ:
Or, if we are working with BsonDocuments and we want to build queries dynamically:
The LINQ syntax supports paging:
And, of course, you can also do it dynamically on a BsonDocument, with a little more work:
Finally, here’s how to do projections using LINQ syntax:
And dynamically defined fields:
This one’s easy:
You can also do it for a single document, the API is almost identical:
MongoDB is not an ORM, which means that some things that you may be used to – lazy loading, change tracking, first level cache, Unit of Work and transactions, etc – do not apply. Having said that, it is totally possible to hide both an ORM and MongoDB under a common abstraction, like the Repository pattern. Maybe a topic for some other time!
Hope this was enough to raise your interest! The next post will be about DDL operations, in the meantime, I’d be happy to hear from you!