Introduction to MongoDb with .NET part 13: starting with the MongoDb .NET driver
April 20, 2016 Leave a comment
Introduction
In the previous post we looked at how to delete documents from a collection in the MongoDb shell. We saw that it wasn’t an overly complex operation. We just have to call the remove method on a collection and supply a query document like the ones we provide to the find function.
In this post we’ll start looking into how to communicate with MongoDb from a .NET project through the MongoDb .NET driver.
The .NET driver
Before we go on make sure you start a command prompt and start the MongoDb server with the mongod command. Next create a new .NET console project and give it some name, it doesn’t really matter. I’ve called mine MongoDbDotNet. Add a folder called Repository and one more called Infrastructure to the project. Normally they should be in a separate C# libraries but we’ll go for a simplified structure in this demo.
The MongoDb .NET driver is available through NuGet. Add the following package to the project:
At the time of writing this post the latest stable version was 2.2.3. By the time you read this there may be a more recent driver.
Just like an SQL Server driver such as EntityFramework the MongoDb driver will also need a connection string. There’s a whole list of examples on how to construct MongoDb connection strings here. Open app.config and add the following connection string to the connectionStrings section:
<connectionStrings> <add connectionString="mongodb://localhost:27017" name="MongoDb"/> </connectionStrings>
The example presented above is probably as easy as it gets: we’re connecting to the local MongoDb server on the default port 27017. More realistic connection strings might be much longer with other properties such as authentication, server names in a database cluster, read and write policies etc. For our purposes this is just fine as a starting point.
We’ll also add the database and collection names in the application settings section in app.config:
<appSettings> <add key="RestaurantsCollectionName" value="restaurants"/> <add key="ZipCodesCollectionName" value="zipcodes"/> <add key="PeopleCollectionName" value="people"/> <add key="DemoDatabaseName" value="model" /> </appSettings>
Next we’ll start building a context object to easily access the database and the collections. Our first goal is to connect to the database.
The MongoDb object context
As a .NET developer you’re probably familiar with the notion of the object context from EntityFramework. It is a complex object that offers handles to directly access the objects in the database, update them, delete them, add new ones etc. It also has a lot more functionality for things like state tracking. EntityFramework offers a wide range of automation tools to the developers. Just think of features like code-first, database-first and model-first. We also have automated migration and an initial seeding for the DB tables.
Here comes the first major difference between using MS SQL with EF in a .NET project and using MongoDb: there’s almost none of the “magic” tools of EF in MongoDb. There’s no built-in context which you can call “SaveChanges” on, there’s no migration tool, there’s no Seed method to fill the database tables, i.e. collections etc
.
Anyhow, the migration tool wouldn’t make any sense in MongoDb because there’s no strict schema in MongoDb collections. It’s fine to insert a Car object in a collection which is then followed by a House object. It’s perfectly legal to place them in the same document. Now, in practice you probably won’t do anything like that. However, you are free to add and remove properties to and from an object and not worry about data migration.
Infrastructure dependencies
Next we’ll create a couple of abstractions and implementations for those abstractions. The abstractions come in the form of two interfaces to read from the configuration source. The project settings, like the connection string and application settings can come from a variety of sources, not just the config file: the database, an external file, a web service etc.
Insert the following interfaces into the Infrastructure folder:
namespace MongoDbDotNet.Infrastructure { public interface IConfigurationRepository { T GetConfigurationValue<T>(string key); T GetConfigurationValue<T>(string key, T defaultValue); } }
namespace MongoDbDotNet.Infrastructure { public interface IConnectionStringRepository { string ReadConnectionString(string connectionStringName); } }
…and here come two implementations. You can put them in the Infrastructure folder as well:
using System.Configuration; namespace MongoDbDotNet.Infrastructure { public class AppConfigConnectionStringRepository : IConnectionStringRepository { public string ReadConnectionString(string connectionStringName) { return ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; } } }
using System; using System.Collections.Generic; using System.Configuration; namespace MongoDbDotNet.Infrastructure { public class ConfigFileConfigurationRepository : IConfigurationRepository { public T GetConfigurationValue<T>(string key) { string value = ConfigurationManager.AppSettings[key]; if (value == null) { throw new KeyNotFoundException("Key " + key + " not found."); } try { if (typeof(Enum).IsAssignableFrom(typeof(T))) return (T)Enum.Parse(typeof(T), value); return (T)Convert.ChangeType(value, typeof(T)); } catch (Exception ex) { throw ex; } } public T GetConfigurationValue<T>(string key, T defaultValue) { string value = ConfigurationManager.AppSettings[key]; if (value == null) { return defaultValue; } try { if (typeof(Enum).IsAssignableFrom(typeof(T))) return (T)Enum.Parse(typeof(T), value); return (T)Convert.ChangeType(value, typeof(T)); } catch (Exception ex) { return defaultValue; } } } }
You’ll need to add a reference to the following library in order to compile the above code:
The MongoDb repository stub
We’ll start building the MongoDb context now. Insert a new class called ModelContext to the Repository folder. We’ll keep it to a minimum to begin with:
using MongoDB.Driver; using MongoDbDotNet.Infrastructure; using System; namespace MongoDbDotNet.Repository { public class ModelContext { private IMongoClient Client { get; set; } private IMongoDatabase Database { get; set; } private static ModelContext _modelContext; private ModelContext() { } public static ModelContext Create(IConfigurationRepository configurationRepository, IConnectionStringRepository connectionStringRepository) { if (_modelContext == null) { _modelContext = new ModelContext(); string connectionString = connectionStringRepository.ReadConnectionString("MongoDb"); _modelContext.Client = new MongoClient(connectionString); _modelContext.Database = _modelContext.Client.GetDatabase(configurationRepository.GetConfigurationValue("DemoDatabaseName", "model")); } return _modelContext; } public void TestConnection() { var dbsCursor = _modelContext.Client.ListDatabases(); var dbsList = dbsCursor.ToList(); foreach (var db in dbsList) { Console.WriteLine(db); } } } }
The above code creates a ModelContext object using the factory pattern. The Create method will only build one ModelContext and then return it on every new request. In other words we’ll have the same context re-used over and over again. That’s fine, MongoDb is meant to be used like that. There’s absolutely no need to create a brand new instance of the object which handles the connection to the MongoDb server. The underlying mechanism will automatically open and close the connection for you. We won’t get those funny “connection closed” and “row not found or changed” exceptions like in LINQ to Entities. The MongoClient object is the central handle to communicate with a MongoDb database from .NET. We also have some code to test the connection to MongoDb. We simply get hold of the database names and write them to the console.
The first test
We can easily call the TestConnection method from Program.cs as follows:
using MongoDbDotNet.Infrastructure; using MongoDbDotNet.Repository; namespace MongoDbDotNet { class Program { static void Main(string[] args) { ModelContext modelContext = ModelContext.Create(new ConfigFileConfigurationRepository(), new AppConfigConnectionStringRepository()); modelContext.TestConnection(); } } }
If all goes well then you’ll see the list of databases available on mongod. I have a couple of databases in place from previous demos:
{ "name" : "loadtest", "sizeOnDisk" : 147456.0, "empty" : false } { "name" : "local", "sizeOnDisk" : 73728.0, "empty" : false } { "name" : "model", "sizeOnDisk" : 6684672.0, "empty" : false }
Therefore don’t worry if you don’t see e.g. “loadtest” in your output. “Model” should be there at least if you’ve followed this series so far.
Great, we have the stub for our database handling code. We’ll build on this in the next post.
You can view all posts related to data storage on this blog here.