Welcome to the Day 3 of the series. Please refer to the previous posts in the series in case you missed it or in case you may want to have a look at the basics
So far we have learned how to start and use Mongo Server using Mongo Shell. Today we will learn:
- Organization of C# Official MongoDB Driver.
- CRUD operations using MongoDB C# Driver in ASP.NET MVC 4
Even though it's called C# Driver, it does work for the entire .NET platform. Here I assume that you've downloaded/installed the installer or Zip file for the C# Official Mongo Driver.
When you install the driver or when you unzip the Zip file, you'll find two libraries inside targeting the .Net Framework 3.5 and higher as in the following:
- The first is the BSON library: this library is a set of abstractions in C# to handle the BSON specifications.
- The Second is the Driver library: this library contains the components to connect, execute commands, and get results from a Mongo Server.
When you add this package via Nuget you'll see these two added to the Projects' References.
If you're installing it using the setup (MSI) file or you're referring to the Library Zip file then you will need to add a reference to the two libraries mentioned above to get started in ASP.NET.
Driver Abstractions
There're three primary levels of abstractions in Mongo.
- Documents are stored in a collection. The driver provides a MongoCollection type to interact with collections.
- Collections are grouped into a database. The Driver provides a MongoDatabase type to interact with databases.
- A server hosts a set of databases.
Let's explore how these abstractions work together to allow us to interact with a Mongo Server all the way down to the level of a collection.
Connecting to a server starts out with an instance of MongoClient. The Mongo Client forms the highest point in the hierarchy of settings for interacting with Mongo. Settings cascade down to the other abstractions. MongoClient's constructor has several forms, the most common of which takes the connection string. Connection strings are URIs that start with a “mongodb” prefix. For example, here's the connection string for a local Mongo Server: “mongodb://localhost” for standard port (27017). If you use a non-standard port then you can specify that as well. You may want to check out the other available options for Mongo Server connection strings: http://docs.mongodb.org/manual/reference/connection-string.
Once we have an instance of a MongoClient, we can use the GetServer() method to access the Mongo Server abstraction. The settings in the MongoServer type class is inherited from MongoClient. A Mongo Server instance allows you to create, drop a database or check if it exists. It also allows getting all database names and getting a specific database by database name.
A MongoDatabase instance allows us to check if a collection exists, create/rename/drop a collection and get all the collection names. There are other many features available that we will explore later in this series.
Setting up an MVC app to perform CRUD Operations
Start Visual Studio and create a new ASP.NET MVC 4 Project as in the following:
I'll select the “Internet Application” template since it comes with a Home Controller and Index View and that will save our efforts in understanding the concept.
This is how it now looks:
We need to add references to the two libraries that we have recently talked about. I'm using the Nuget Package Manager console here but you're free to use any of the methods specified in a previous article.
Make sure the Mongo Server is running so we can do the DB operations.
We will modify the Home Controller to connect to the Mongo db to store our data. I will be using an Employees database for demo purposes. We need to create an instance of Mongo Client first in order to proceed.
Let's add a constructor to this controller and create an instance of MongoClient inside. The Mongo Client needs a connection string. Connection Strings are best kept as settings of our project, so let's create that as a setting.
Go to the “Properties” of the project and select “Settings”.
Click the link to create settings file.
Add a new setting named “EmployeesConnectionString” and select the type as ConnectionString. Set the default value for now to use the local server we've just started.
Similarly if you want to keep the database name configurable then add the second setting also in a similar way. I've added a setting “DB” with the value “Employees” for database name.
In order to use Settings we need to add a reference to the <project name>.Properties in our controller. In our case this will read like this:
using MongoDB_CRUD_Ops_In_ASP.NET_MVC.Properties;
Let's get back to our controller and pass the connection string to the Mongo Client we're going to create.
The Mongo client is a new abstraction to interact with the server. At this point the next step is to use the GetServer() method on the client instance and then with the Mongo server instance we can access a specific database. You can create or refer to a Mongo database by calling the GetDatabase() method and passing a database name. It's a good idea to store this in a configurable setting. So open the settings again. Add a new setting named “Employees”, leave the type as string.
Close the settings and use the database name setting to access the database. We now have access to an abstraction to interact with our Employees database.
Here's the modified controller code with constructor:
You can now use the GetCollection() method on a database to get all the collections inside but before proceeding and storing our data in the Employees database, let's modify our Index action to check if everything is good and working so far.
Let's add some code to return Mongo Server Build information from the Index action. It supports late execution since the driver won't actually connect until the first command is executed, it could be any command.
Here I'm using the MongoDatabase.GetStats() method that returns database stats as a BSON document, just connect to the server and then I'm accessing the property MongoDatabase.Server.BuildInfo.
Now hit CTRL+F5 and see if it shows the build info, this is my output on the screen:
If you don't execute any command before accessing the property then it will return an empty string because it won't have any information.
Have you noticed that we don't have any code to connect/disconnect or dispose the Mongo connection? Yes, you guessed it right, the Mongo driver handles it all so you don't need to worry about it.
Okay, let's move to the real story. We need to perform CRUD operations on the Mongo database using the Mongo C# Official Driver.
We already have a constructor that I've moved to a repository for the sake of simplicity, ease and code reuse.
Here's the Employee model that I'm using:
Here's the repository interface:
And here's the implementation:
using MongoDB.Driver;
using MongoDB.Driver.Builders;
using MongoDB_CRUD_Ops_In_ASP.NET_MVC.Properties;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MongoDB_CRUD_Ops_In_ASP.NET_MVC.Employees
{
public class EmployeeRepository : IEmployeeRepository
{
public MongoDatabase MongoDatabase;
public MongoCollection EmployeesCollection;
public bool ServerIsDown = false;
// Constructor
public EmployeeRepository()
{
// Get the Mongo Client
var mongoClient = new MongoClient(Settings.Default.EmployeesConnectionString);
// Get the Mongo Server from the Cliet Instance
var server = mongoClient.GetServer();
// Assign the database to mongoDatabase
MongoDatabase = server.GetDatabase(Settings.Default.DB);
// get the Employees collection (table) and assign to EmployeesCollection
// "Employees"- db name is same as collection (table) name.
EmployeesCollection = MongoDatabase.GetCollection("Employees");
//test if server is up and running
try
{
MongoDatabase.Server.Ping();
// Ping() method throws exception if not able to connect
}
catch (Exception ex)
{
ServerIsDown = true;
}
}
#region Test Data
private Employee[] _testEmployeeData = new Employee[]
{
new Employee()
{
Id = Guid.NewGuid().ToString(),
FirstName = "Sunny",
LastName = "Kumar",
Address = "New Delhi",
ContactNo = "99999999"
},
new Employee()
{
Id = Guid.NewGuid().ToString(),
FirstName = "Manish",
LastName = "Kumar",
Address = "Mumbai",
ContactNo = "99999999"
},
new Employee()
{
Id = Guid.NewGuid().ToString(),
FirstName = "Gyanesh",
LastName = "Singh",
Address = "Chennai",
ContactNo = "99999999"
},
new Employee()
{
Id = Guid.NewGuid().ToString(),
FirstName = "Suresh",
LastName = "Singh",
Address = "Gurgaon",
ContactNo = "99999999"
},
new Employee()
{
Id = Guid.NewGuid().ToString(),
FirstName = "Mahesh",
LastName = "Kumar",
Address = "Patna",
ContactNo = "99999999"
},
new Employee()
{
Id = Guid.NewGuid().ToString(),
FirstName = "Sukesh",
LastName = "Kumar",
Address = "Varanasi",
ContactNo = "99999999"
},
new Employee()
{
Id = Guid.NewGuid().ToString(),
FirstName = "Sumit",
LastName = "Anand",
Address = "New Delhi",
ContactNo = "99999999"
}
};
#endregion
private List<Employee> _employeesList = new List<Employee>();
public IEnumerable<Employee> GetAllEmployees()
{
if (ServerIsDown) return null;
if (Convert.ToInt32(EmployeesCollection.Count()) > 0)
{
_employeesList.Clear();
var employees = EmployeesCollection.FindAs(typeof (Employee), Query.NE("FirstName", "null"));
if (employees.Count() > 0)
{
foreach (Employee employee in employees)
{
_employeesList.Add(employee);
}
}
}
else
{
#region add test data if DB is empty
EmployeesCollection.RemoveAll();
foreach (var employee in _testEmployeeData)
{
_employeesList.Add(employee);
Add(employee); // add data to mongo db also
}
#endregion
}
var result = _employeesList.AsQueryable();
return result;
}
public Employee GetEmployeeById(string id)
{
if (string.IsNullOrEmpty(id))
{
throw new ArgumentNullException("id", "Employee Id is empty!");
}
var employee = (Employee) EmployeesCollection.FindOneAs(typeof (Employee), Query.EQ("_id", id));
return employee;
}
public Employee Add(Employee employee)
{
if (string.IsNullOrEmpty(employee.Id))
{
employee.Id = Guid.NewGuid().ToString();
}
EmployeesCollection.Save(employee);
return employee;
}
public bool Update(string objectId, Employee employee)
{
UpdateBuilder updateBuilder = MongoDB.Driver.Builders.Update
.Set("FirstName", employee.FirstName)
.Set("LastName", employee.LastName)
.Set("Address", employee.Address)
.Set("ContactNo", employee.ContactNo);
EmployeesCollection.Update(Query.EQ("_id", objectId), updateBuilder);
return true;
}
public bool Delete(string objectId)
{
EmployeesCollection.Remove(Query.EQ("_id", objectId));
return true;
}
}
}
And the controller HomeController.cs:
using MongoDB_CRUD_Ops_In_ASP.NET_MVC.Employees;
using System.Web.Mvc;
namespace MongoDB_CRUD_Ops_In_ASP.NET_MVC.Controllers
{
public class HomeController : Controller
{
public EmployeeRepository Context = new EmployeeRepository();
public ActionResult Index()
{
//return Json(MongoDatabase.Server.BuildInfo,JsonRequestBehavior.AllowGet); // for testing purposes
//return View();
return View("Index", Context.GetAllEmployees());
}
public ActionResult Add()
{
return View();
}
[HttpPost]
public ActionResult Add(Employee employee)
{
var result = Context.Add(employee);
return RedirectToAction("Index");
}
public ActionResult Edit(string Id)
{
var employee = Context.GetEmployeeById(Id);
return View(Context.GetEmployeeById(Id));
}
[HttpPost]
public ActionResult Edit(Employee employee)
{
if (employee == null) return RedirectToAction("Index");
Context.Update(employee.Id, employee);
return RedirectToAction("Index");
}
public ActionResult Delete(string Id)
{
Context.Delete(Id);
return RedirectToAction("Index");
}
}
}
The rest of the code for the View you can find inside the sample application that is available for download with this article.
Let's explore the important points in the EmployeesRepository.cs code.
The method:
<Collection>.save(<document>)
allows us to save a new document in the collection, whereas the method:
<Collection>.update(<selector query document>,<updated document>,<UpdateFlag>)
allows you to update an existing document. You pass a query document as the first parameter that basically represents the “where” clause in our normal SQL statement. You specify the conditions for what document(s) must be updated. The second parameter takes the updated document that will replace the values in the existing documents. The third parameter “UpdateFlag” is optional that specifies if you want to update all the documents found with a selector query document or you want to update the first one only. You'll find a couple of other overloads also.
The Mongo C# Driver makes your work easier using the Mongo shell. Type in all the JSON code to find or update the documents. It comes with a Query and UpdateBuilder class that helps you prepare query documents and update the collection as in the following:
It offers an object oriented way of doing things as it is supposed to. Coming to the next part of the code, the Home Controller contains all the necessary actions and codes for performing CRUD operations using the EmployeeRepository and I believe there's nothing to explain. Everything inside is self-explanatory.
SummaryIn this article we learned the abstraction of the Mongo C# Official Driver. We learned how to use this driver to interact with the Mongo server. We learned how this driver reduces our effort of interacting with the Mongo Server. We also learned how to perform CRUD operations using this driver on MongoDB.
That's all for now folks, see you on the next day!
I hope you enjoyed this article, thanks for reading! Feedback and comments are highly appreciated!!