Skip to main content

MongoDB .NET Driver

Introduction

The MongoDB .NET Driver is the official driver that allows C# and other .NET applications to interact with MongoDB databases. It provides a powerful, type-safe, and idiomatic way to work with MongoDB directly from your .NET applications. Whether you're building web applications with ASP.NET, desktop applications, or microservices, the MongoDB .NET Driver enables seamless integration with your MongoDB databases.

In this tutorial, we'll explore how to use the MongoDB .NET Driver, from basic setup and connection to performing CRUD operations and more advanced features. By the end, you'll have a solid foundation for incorporating MongoDB into your .NET projects.

Getting Started

Installation

To get started with the MongoDB .NET Driver, you need to install the NuGet package in your project. You can do this either through the NuGet Package Manager or the .NET CLI:

Using the Package Manager Console:

csharp
Install-Package MongoDB.Driver

Using the .NET CLI:

csharp
dotnet add package MongoDB.Driver

Basic Connection

Once installed, you can connect to a MongoDB server using the MongoClient class. Here's a basic example of establishing a connection:

csharp
using MongoDB.Driver;

// Create a client
var connectionString = "mongodb://localhost:27017";
var client = new MongoClient(connectionString);

// Get a database
var database = client.GetDatabase("myDatabase");

// Get a collection
var collection = database.GetCollection<BsonDocument>("myCollection");

If you need to connect to a MongoDB Atlas cluster or a server with authentication:

csharp
var connectionString = "mongodb+srv://username:[email protected]/myDatabase";
var client = new MongoClient(connectionString);

Creating a Data Model

While you can work with BsonDocument objects directly, it's often better to define a C# class to represent your documents. This provides type safety and a more natural programming experience.

csharp
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;

public class Product
{
[BsonId]
public ObjectId Id { get; set; }

[BsonElement("name")]
public string Name { get; set; }

[BsonElement("price")]
public decimal Price { get; set; }

[BsonElement("category")]
public string Category { get; set; }

[BsonElement("inStock")]
public bool InStock { get; set; }

[BsonElement("tags")]
public List<string> Tags { get; set; }
}

In this example:

  • [BsonId] marks the property as the document's ID
  • [BsonElement("name")] specifies the field name in the MongoDB document

Now you can work with the Product class in your collection:

csharp
var products = database.GetCollection<Product>("products");

CRUD Operations

Create (Insert)

Let's look at how to insert documents into a MongoDB collection:

Inserting One Document

csharp
var product = new Product
{
Name = "Laptop",
Price = 1299.99m,
Category = "Electronics",
InStock = true,
Tags = new List<string> { "computer", "tech", "portable" }
};

await products.InsertOneAsync(product);
Console.WriteLine($"Inserted product with ID: {product.Id}");

Inserting Multiple Documents

csharp
var productList = new List<Product>
{
new Product
{
Name = "Smartphone",
Price = 899.99m,
Category = "Electronics",
InStock = true,
Tags = new List<string> { "phone", "tech", "mobile" }
},
new Product
{
Name = "Headphones",
Price = 199.99m,
Category = "Electronics",
InStock = true,
Tags = new List<string> { "audio", "tech" }
}
};

await products.InsertManyAsync(productList);

Read (Query)

MongoDB .NET Driver provides multiple ways to query documents:

Finding a Single Document

csharp
var filter = Builders<Product>.Filter.Eq(p => p.Name, "Laptop");
var laptop = await products.Find(filter).FirstOrDefaultAsync();

if (laptop != null)
{
Console.WriteLine($"Found: {laptop.Name}, Price: ${laptop.Price}");
}
else
{
Console.WriteLine("Product not found");
}

Finding Multiple Documents

csharp
var categoryFilter = Builders<Product>.Filter.Eq(p => p.Category, "Electronics");
var electronicProducts = await products.Find(categoryFilter).ToListAsync();

Console.WriteLine($"Found {electronicProducts.Count} electronic products:");
foreach (var product in electronicProducts)
{
Console.WriteLine($"- {product.Name}: ${product.Price}");
}

Using Query Operators

MongoDB provides various query operators that can be used through the Filter builders:

csharp
// Find products priced between $200 and $1000
var priceFilter = Builders<Product>.Filter.And(
Builders<Product>.Filter.Gte(p => p.Price, 200),
Builders<Product>.Filter.Lte(p => p.Price, 1000)
);

var affordableProducts = await products.Find(priceFilter).ToListAsync();

// Find products that have the "tech" tag
var tagFilter = Builders<Product>.Filter.AnyEq(p => p.Tags, "tech");
var techProducts = await products.Find(tagFilter).ToListAsync();

Projection

You can limit which fields are returned using projection:

csharp
var projection = Builders<Product>.Projection
.Include(p => p.Name)
.Include(p => p.Price)
.Exclude(p => p.Id);

var filter = Builders<Product>.Filter.Empty;
var simplifiedProducts = await products
.Find(filter)
.Project<dynamic>(projection)
.ToListAsync();

Update

The driver provides methods for updating existing documents:

Update a Single Document

csharp
var filter = Builders<Product>.Filter.Eq(p => p.Name, "Laptop");
var update = Builders<Product>.Update
.Set(p => p.Price, 1199.99m)
.Set(p => p.InStock, false);

var updateResult = await products.UpdateOneAsync(filter, update);
Console.WriteLine($"Modified {updateResult.ModifiedCount} document(s)");

Update Multiple Documents

csharp
var categoryFilter = Builders<Product>.Filter.Eq(p => p.Category, "Electronics");
var update = Builders<Product>.Update.Inc(p => p.Price, -10.00m); // $10 discount

var updateResult = await products.UpdateManyAsync(categoryFilter, update);
Console.WriteLine($"Applied discount to {updateResult.ModifiedCount} product(s)");

Upsert (Update or Insert)

csharp
var filter = Builders<Product>.Filter.Eq(p => p.Name, "Tablet");
var update = Builders<Product>.Update
.SetOnInsert(p => p.Price, 399.99m)
.SetOnInsert(p => p.Category, "Electronics")
.SetOnInsert(p => p.InStock, true)
.SetOnInsert(p => p.Tags, new List<string> { "portable", "tech" });

var updateOptions = new UpdateOptions { IsUpsert = true };
await products.UpdateOneAsync(filter, update, updateOptions);

Delete

Finally, let's see how to delete documents:

Delete a Single Document

csharp
var filter = Builders<Product>.Filter.Eq(p => p.Name, "Headphones");
var deleteResult = await products.DeleteOneAsync(filter);
Console.WriteLine($"Deleted {deleteResult.DeletedCount} document(s)");

Delete Multiple Documents

csharp
var outOfStockFilter = Builders<Product>.Filter.Eq(p => p.InStock, false);
var deleteResult = await products.DeleteManyAsync(outOfStockFilter);
Console.WriteLine($"Deleted {deleteResult.DeletedCount} out-of-stock product(s)");

Advanced Features

Working with Aggregation Pipeline

The MongoDB .NET Driver provides robust support for MongoDB's aggregation pipeline, which allows for complex data transformations and analyses:

csharp
var pipeline = new BsonDocument[]
{
new BsonDocument("$match", new BsonDocument("Category", "Electronics")),
new BsonDocument("$group", new BsonDocument
{
{ "_id", null },
{ "averagePrice", new BsonDocument("$avg", "$price") },
{ "count", new BsonDocument("$sum", 1) }
})
};

var results = await products.Aggregate<BsonDocument>(pipeline).ToListAsync();
foreach (var result in results)
{
Console.WriteLine($"Found {result["count"]} electronic products with average price: ${result["averagePrice"]}");
}

You can also use a more strongly-typed approach:

csharp
var match = Builders<Product>.Filter.Eq(p => p.Category, "Electronics");
var group = new BsonDocument("$group", new BsonDocument
{
{ "_id", "$category" },
{ "averagePrice", new BsonDocument("$avg", "$price") },
{ "count", new BsonDocument("$sum", 1) }
});

var pipeline = new EmptyPipelineDefinition<Product>()
.Match(match)
.AppendStage<BsonDocument>(group);

var results = await products.Aggregate(pipeline).ToListAsync();

Transactions

MongoDB 4.0+ supports multi-document transactions. Here's how to use them with the .NET Driver:

csharp
using (var session = await client.StartSessionAsync())
{
session.StartTransaction();

try
{
var productsCollection = database.GetCollection<Product>("products");
var ordersCollection = database.GetCollection<Order>("orders");

// Update product inventory
var productFilter = Builders<Product>.Filter.Eq(p => p.Name, "Laptop");
var productUpdate = Builders<Product>.Update.Inc(p => p.Quantity, -1);
await productsCollection.UpdateOneAsync(session, productFilter, productUpdate);

// Create an order
var order = new Order
{
ProductName = "Laptop",
Quantity = 1,
OrderDate = DateTime.UtcNow
};
await ordersCollection.InsertOneAsync(session, order);

// Commit the transaction
await session.CommitTransactionAsync();
Console.WriteLine("Transaction completed successfully");
}
catch (Exception ex)
{
await session.AbortTransactionAsync();
Console.WriteLine($"Transaction aborted: {ex.Message}");
}
}

Change Streams

MongoDB change streams allow applications to access real-time data changes. Here's how to use them with the .NET Driver:

csharp
var pipeline = new EmptyPipelineDefinition<ChangeStreamDocument<Product>>()
.Match(change => change.OperationType == ChangeStreamOperationType.Insert ||
change.OperationType == ChangeStreamOperationType.Update);

using (var cursor = await products.WatchAsync(pipeline))
{
foreach (var change in cursor.ToEnumerable())
{
switch (change.OperationType)
{
case ChangeStreamOperationType.Insert:
Console.WriteLine($"New product inserted: {change.FullDocument.Name}");
break;
case ChangeStreamOperationType.Update:
Console.WriteLine($"Product updated: {change.FullDocument.Name}");
break;
}
}
}

Real-World Application: Building a Product Inventory System

Let's combine what we've learned to create a simple product inventory system:

csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;

public class InventoryManager
{
private readonly IMongoCollection<Product> _products;

public InventoryManager(string connectionString, string databaseName)
{
var client = new MongoClient(connectionString);
var database = client.GetDatabase(databaseName);
_products = database.GetCollection<Product>("products");

// Create indexes for better performance
CreateIndexesAsync().Wait();
}

private async Task CreateIndexesAsync()
{
// Create an index on the Name field for faster lookups
var indexKeysDefinition = Builders<Product>.IndexKeys.Ascending(p => p.Name);
await _products.Indexes.CreateOneAsync(new CreateIndexModel<Product>(indexKeysDefinition));

// Create a compound index on Category and Price for efficient category browsing and sorting
var compoundIndex = Builders<Product>.IndexKeys
.Ascending(p => p.Category)
.Descending(p => p.Price);
await _products.Indexes.CreateOneAsync(new CreateIndexModel<Product>(compoundIndex));
}

public async Task<Product> AddProductAsync(Product product)
{
await _products.InsertOneAsync(product);
return product;
}

public async Task<List<Product>> GetAllProductsAsync()
{
return await _products.Find(p => true).ToListAsync();
}

public async Task<List<Product>> GetProductsByCategoryAsync(string category)
{
var filter = Builders<Product>.Filter.Eq(p => p.Category, category);
return await _products.Find(filter).ToListAsync();
}

public async Task<bool> UpdateProductStockAsync(string productName, int quantityChange)
{
var filter = Builders<Product>.Filter.Eq(p => p.Name, productName);
var update = Builders<Product>.Update.Inc("quantity", quantityChange);

var result = await _products.UpdateOneAsync(filter, update);
return result.ModifiedCount > 0;
}

public async Task<ProductStats> GetProductStatsAsync()
{
var pipeline = new[]
{
new BsonDocument("$group", new BsonDocument
{
{ "_id", null },
{ "totalProducts", new BsonDocument("$sum", 1) },
{ "averagePrice", new BsonDocument("$avg", "$price") },
{ "inStockCount", new BsonDocument("$sum", new BsonDocument(
"$cond", new BsonArray { "$inStock", 1, 0 }
)) }
})
};

var results = await _products.Aggregate<BsonDocument>(pipeline).ToListAsync();

if (!results.Any())
return new ProductStats { TotalProducts = 0, AveragePrice = 0, InStockCount = 0 };

var stats = results.First();
return new ProductStats
{
TotalProducts = stats["totalProducts"].AsInt32,
AveragePrice = stats["averagePrice"].AsDouble,
InStockCount = stats["inStockCount"].AsInt32
};
}
}

public class ProductStats
{
public int TotalProducts { get; set; }
public double AveragePrice { get; set; }
public int InStockCount { get; set; }
}

Usage example:

csharp
// Example program using the inventory manager
class Program
{
static async Task Main(string[] args)
{
var connectionString = "mongodb://localhost:27017";
var dbName = "inventory";

var manager = new InventoryManager(connectionString, dbName);

// Add some products
await manager.AddProductAsync(new Product
{
Name = "Gaming Laptop",
Price = 1499.99m,
Category = "Computers",
InStock = true,
Quantity = 10,
Tags = new List<string> { "gaming", "laptop", "high-performance" }
});

await manager.AddProductAsync(new Product
{
Name = "Bluetooth Speaker",
Price = 79.99m,
Category = "Audio",
InStock = true,
Quantity = 25,
Tags = new List<string> { "speaker", "wireless", "portable" }
});

// Get all products
var allProducts = await manager.GetAllProductsAsync();
Console.WriteLine($"Total products: {allProducts.Count}");

// Update stock
bool updated = await manager.UpdateProductStockAsync("Gaming Laptop", -2);
Console.WriteLine($"Stock updated: {updated}");

// Get statistics
var stats = await manager.GetProductStatsAsync();
Console.WriteLine($"Statistics: {stats.TotalProducts} products, ${stats.AveragePrice:F2} average price, {stats.InStockCount} in stock");
}
}

Performance Considerations

When working with the MongoDB .NET Driver, keep these performance tips in mind:

  1. Use proper indexes to speed up queries
  2. Limit the fields returned using projection when you don't need the entire document
  3. Use batch operations (InsertMany, UpdateMany) instead of individual operations when possible
  4. Monitor slow queries using MongoDB's profiler
  5. Consider read preferences when working with replica sets
  6. Use connection pooling properly - the driver manages this for you but be aware of its settings
  7. Consider implementing caching for frequently accessed, rarely changing data

Summary

In this comprehensive guide, we've explored the MongoDB .NET Driver and how to use it effectively in your C# applications. We've covered:

  • Installation and basic setup
  • Creating data models with proper MongoDB attributes
  • Performing CRUD operations (Create, Read, Update, Delete)
  • Advanced features including aggregation, transactions, and change streams
  • Building a practical inventory management system as a real-world example
  • Performance considerations for production applications

The MongoDB .NET Driver provides a powerful and flexible way to work with MongoDB databases from .NET applications. By leveraging its extensive feature set, you can build robust, scalable applications that take full advantage of MongoDB's capabilities.

Additional Resources

Here are some resources to further your learning:

Exercises

  1. Basic CRUD: Create a simple application that manages a collection of books, allowing for adding, retrieving, updating, and deleting book information.

  2. Advanced Queries: Build a query system for an e-commerce application that can filter products by multiple criteria (price range, category, tags, etc.).

  3. Aggregation Challenge: Use the aggregation pipeline to analyze sales data, grouping by month and category to generate a sales report.

  4. Change Stream Monitor: Create a monitoring application that watches for changes to a collection and logs them in real-time.

  5. Pagination Implementation: Implement a paginated product catalog that efficiently loads 10 products at a time from a large collection.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)