MongoDB Mastery: CRUD Operations with Real-World Examples - Part 2
Master Create, Read, Update, Delete operations in MongoDB with comprehensive examples including complex queries, indexing, and a complete e-commerce product catalog system using C# and .NET.
MongoDB Mastery: CRUD Operations with Real-World Examples - Part 2
Introduction
Welcome back to our MongoDB Mastery series! In Part 1, we covered MongoDB installation, basic concepts, and initial setup with C# and the .NET driver. Now we'll dive deep into CRUD operations (Create, Read, Update, Delete) with practical, real-world examples.
This part focuses on:
- Advanced querying techniques with LINQ and filter definitions
- Indexing for performance optimization
- Complex CRUD operations with the MongoDB .NET driver
- A complete e-commerce product catalog system
- API design and implementation
📚 Series Overview:
- Part 1: Setup and Getting Started
- Part 2: CRUD Operations with Real-World Examples (This article)
- Part 3: Advanced Features and Performance Optimization
Section 1: Advanced Querying Techniques
Comparison Operators
using MongoDB.Driver;
using MongoDB.Bson;
// Find products in price range
var affordableProducts = await products.Find(p => p.Price >= 10 && p.Price <= 100).ToListAsync();
// Find products NOT in a category
var nonElectronics = await products.Find(p => p.Category != "electronics").ToListAsync();
// Find products with multiple conditions using LINQ
var premiumItems = await products.Find(p =>
p.Price > 500 &&
p.Rating >= 4.5 &&
p.InStock == true
).ToListAsync();
// Using FilterDefinition for complex queries
var filter = Builders<Product>.Filter.And(
Builders<Product>.Filter.Gt(p => p.Price, 500),
Builders<Product>.Filter.Gte(p => p.Rating, 4.5),
Builders<Product>.Filter.Eq(p => p.InStock, true)
);
var premiumItemsAlt = await products.Find(filter).ToListAsync();
Logical Operators
// OR condition with LINQ
var searchResults = await products.Find(p =>
p.Name.ToLower().Contains("laptop") ||
p.Tags.Contains("electronics")
).ToListAsync();
// NOR (neither condition) using FilterDefinition
var norFilter = Builders<Product>.Filter.Nor(
Builders<Product>.Filter.Gt(p => p.Price, 1000),
Builders<Product>.Filter.Eq(p => p.Category, "luxury")
);
var nonPremiumItems = await products.Find(norFilter).ToListAsync();
// Complex nested conditions
var complexFilter = Builders<Product>.Filter.And(
Builders<Product>.Filter.Or(
Builders<Product>.Filter.Regex(p => p.Name, new BsonRegularExpression("laptop", "i")),
Builders<Product>.Filter.AnyIn(p => p.Tags, new[] { "electronics", "gaming" })
),
Builders<Product>.Filter.ElemMatch(p => p.Reviews,
Builders<Review>.Filter.Gte(r => r.Rating, 4)
)
);
Array Operators
// Find products with specific tags
var taggedProducts = await products.Find(p =>
p.Tags.Contains("electronics") ||
p.Tags.Contains("gaming")
).ToListAsync();
// Find products where tags array contains all specified values
var multiTagged = await products.Find(p =>
p.Tags.Contains("wireless") &&
p.Tags.Contains("bluetooth")
).ToListAsync();
// Find products with array size
var singleTagProducts = await products.Find(p => p.Tags.Count == 1).ToListAsync();
// Using FilterDefinition for array operations
var allTagsFilter = Builders<Product>.Filter.All(p => p.Tags, new[] { "wireless", "bluetooth" });
var multiTaggedAlt = await products.Find(allTagsFilter).ToListAsync();
Element Operators
// Find documents with specific field
var ratedProducts = await products.Find(p => p.Rating != null).ToListAsync();
// Find documents by field type (more complex in C#)
var stringFields = await products.Find(p => p.Name is string && p.Name.Length > 0).ToListAsync();
// Check for field existence using FilterDefinition
var ratingExistsFilter = Builders<Product>.Filter.Exists(p => p.Rating);
var ratedProductsAlt = await products.Find(ratingExistsFilter).ToListAsync();
Section 2: Indexing for Performance
Creating Indexes
// Single field index
await products.Indexes.CreateOneAsync(
new CreateIndexModel<Product>(Builders<Product>.IndexKeys.Ascending(p => p.Price))
);
// Compound index
await products.Indexes.CreateOneAsync(
new CreateIndexModel<Product>(Builders<Product>.IndexKeys.Ascending(p => p.Category).Descending(p => p.Price))
);
// Text index for search
await products.Indexes.CreateOneAsync(
new CreateIndexModel<Product>(Builders<Product>.IndexKeys.Text(p => p.Name).Text(p => p.Description))
);
// Unique index
await products.Indexes.CreateOneAsync(
new CreateIndexModel<Product>(
Builders<Product>.IndexKeys.Ascending(p => p.Sku),
new CreateIndexOptions { Unique = true }
)
);
// Partial index
await products.Indexes.CreateOneAsync(
new CreateIndexModel<Product>(
Builders<Product>.IndexKeys.Ascending(p => p.Price),
new CreateIndexOptions
{
PartialFilterExpression = Builders<Product>.Filter.Eq(p => p.InStock, true)
}
)
);
// Compound text index with weights
await products.Indexes.CreateOneAsync(
new CreateIndexModel<Product>(
Builders<Product>.IndexKeys.Combine(
Builders<Product>.IndexKeys.Text(p => p.Name),
Builders<Product>.IndexKeys.Text(p => p.Description),
Builders<Product>.IndexKeys.Text(p => p.Tags)
),
new CreateIndexOptions
{
Weights = new BsonDocument
{
{ "Name", 10 },
{ "Description", 5 },
{ "Tags", 3 }
}
}
)
);
Index Management
// List all indexes
using (var cursor = await products.Indexes.ListAsync())
{
var indexes = await cursor.ToListAsync();
foreach (var index in indexes)
{
Console.WriteLine($"Index: {index["name"]}");
}
}
// Drop specific index
await products.Indexes.DropOneAsync("Price_1");
// Drop all indexes
await products.Indexes.DropAllAsync();
Index Best Practices
- Create indexes on frequently queried fields
- Use compound indexes for multiple query conditions
- Consider index selectivity (unique values vs total documents)
- Monitor index usage with ExplainAsync
- Avoid over-indexing (each index has maintenance overhead)
Section 3: Real-World Example - E-Commerce Product Catalog
Let's build a comprehensive product catalog system with full CRUD operations.
Product Model
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using System.Text.Json.Serialization;
namespace ECommerce.Models
{
public class Product
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
[BsonElement("sku")]
public string Sku { get; set; }
[BsonElement("name")]
public string Name { get; set; }
[BsonElement("description")]
public string Description { get; set; }
[BsonElement("price")]
[BsonRepresentation(BsonType.Decimal128)]
public decimal Price { get; set; }
[BsonElement("category")]
public string Category { get; set; }
[BsonElement("brand")]
public string Brand { get; set; }
[BsonElement("tags")]
public List<string> Tags { get; set; } = new List<string>();
[BsonElement("images")]
public List<string> Images { get; set; } = new List<string>();
[BsonElement("specifications")]
public Dictionary<string, string> Specifications { get; set; } = new Dictionary<string, string>();
[BsonElement("inventory")]
public Inventory Inventory { get; set; } = new Inventory();
[BsonElement("rating")]
public Rating Rating { get; set; } = new Rating();
[BsonElement("reviews")]
public List<Review> Reviews { get; set; } = new List<Review>();
[BsonElement("isActive")]
public bool IsActive { get; set; } = true;
[BsonElement("createdAt")]
[BsonDateTimeOptions(Kind = DateTimeKind.Utc)]
public DateTime CreatedAt { get; set; }
[BsonElement("updatedAt")]
[BsonDateTimeOptions(Kind = DateTimeKind.Utc)]
public DateTime? UpdatedAt { get; set; }
// Calculated properties
[BsonIgnore]
public int AvailableStock => Math.Max(0, Inventory.Quantity - Inventory.Reserved);
[BsonIgnore]
public bool IsLowStock => AvailableStock <= Inventory.LowStockThreshold;
}
public class Inventory
{
[BsonElement("quantity")]
public int Quantity { get; set; }
[BsonElement("reserved")]
public int Reserved { get; set; }
[BsonElement("lowStockThreshold")]
public int LowStockThreshold { get; set; } = 5;
}
public class Rating
{
[BsonElement("average")]
public double Average { get; set; }
[BsonElement("count")]
public int Count { get; set; }
}
public class Review
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
[BsonElement("userId")]
[BsonRepresentation(BsonType.ObjectId)]
public string UserId { get; set; }
[BsonElement("userName")]
public string UserName { get; set; }
[BsonElement("rating")]
public int Rating { get; set; }
[BsonElement("comment")]
public string Comment { get; set; }
[BsonElement("createdAt")]
[BsonDateTimeOptions(Kind = DateTimeKind.Utc)]
public DateTime CreatedAt { get; set; }
}
}
Product Service with Full CRUD
using MongoDB.Driver;
using ECommerce.Models;
using MongoDB.Bson;
using System.Text.RegularExpressions;
namespace ECommerce.Services
{
public class ProductService
{
private readonly IMongoCollection<Product> _products;
public ProductService(IMongoDatabase database)
{
_products = database.GetCollection<Product>("products");
}
public async Task<Product> CreateProductAsync(Product product)
{
// Check if SKU already exists
var existingProduct = await _products.Find(p => p.Sku == product.Sku).FirstOrDefaultAsync();
if (existingProduct != null)
{
throw new InvalidOperationException("Product with this SKU already exists");
}
product.CreatedAt = DateTime.UtcNow;
await _products.InsertOneAsync(product);
return product;
}
public async Task<Product> GetProductByIdAsync(string id)
{
return await _products.Find(p => p.Id == id && p.IsActive).FirstOrDefaultAsync();
}
public async Task<Product> GetProductBySkuAsync(string sku)
{
return await _products.Find(p => p.Sku == sku && p.IsActive).FirstOrDefaultAsync();
}
public async Task<List<Product>> GetAllProductsAsync(ProductFilter filter = null, ProductSort sort = null, Pagination pagination = null)
{
var query = _products.Find(p => p.IsActive);
// Apply filters
if (filter != null)
{
var filterDefinition = BuildFilterDefinition(filter);
query = query.Match(filterDefinition);
}
// Apply sorting
if (sort != null)
{
var sortDefinition = BuildSortDefinition(sort);
query = query.Sort(sortDefinition);
}
// Apply pagination
if (pagination != null)
{
query = query.Skip(pagination.Skip).Limit(pagination.Limit);
}
return await query.ToListAsync();
}
public async Task<List<Product>> SearchProductsAsync(string searchTerm, ProductFilter filter = null, Pagination pagination = null)
{
if (string.IsNullOrWhiteSpace(searchTerm))
{
return await GetAllProductsAsync(filter, null, pagination);
}
var searchFilter = Builders<Product>.Filter.And(
Builders<Product>.Filter.Eq(p => p.IsActive, true),
Builders<Product>.Filter.Text(searchTerm)
);
if (filter != null)
{
var additionalFilter = BuildFilterDefinition(filter);
searchFilter = Builders<Product>.Filter.And(searchFilter, additionalFilter);
}
var query = _products.Find(searchFilter)
.Sort(Builders<Product>.Sort.MetaTextScore("textScore"));
if (pagination != null)
{
query = query.Skip(pagination.Skip).Limit(pagination.Limit);
}
return await query.ToListAsync();
}
public async Task<bool> UpdateProductAsync(string id, Product updatedProduct)
{
// Prevent updating SKU if it would conflict
if (!string.IsNullOrEmpty(updatedProduct.Sku))
{
var existingProduct = await _products.Find(p =>
p.Sku == updatedProduct.Sku &&
p.Id != id
).FirstOrDefaultAsync();
if (existingProduct != null)
{
throw new InvalidOperationException("SKU already exists for another product");
}
}
updatedProduct.UpdatedAt = DateTime.UtcNow;
var result = await _products.ReplaceOneAsync(p => p.Id == id, updatedProduct);
return result.ModifiedCount > 0;
}
public async Task<bool> UpdateInventoryAsync(string id, int quantityChange, InventoryOperation operation = InventoryOperation.Set)
{
UpdateDefinition<Product> update;
switch (operation)
{
case InventoryOperation.Increment:
update = Builders<Product>.Update.Inc(p => p.Inventory.Quantity, quantityChange);
break;
case InventoryOperation.Decrement:
update = Builders<Product>.Update.Inc(p => p.Inventory.Quantity, -Math.Abs(quantityChange));
break;
default:
update = Builders<Product>.Update.Set(p => p.Inventory.Quantity, quantityChange);
break;
}
update = Builders<Product>.Update.Combine(update,
Builders<Product>.Update.Set(p => p.UpdatedAt, DateTime.UtcNow)
);
var result = await _products.UpdateOneAsync(p => p.Id == id, update);
return result.ModifiedCount > 0;
}
public async Task<bool> AddReviewAsync(string productId, Review review)
{
review.Id = ObjectId.GenerateNewId().ToString();
review.CreatedAt = DateTime.UtcNow;
var update = Builders<Product>.Update.Combine(
Builders<Product>.Update.Push(p => p.Reviews, review),
Builders<Product>.Update.Set(p => p.UpdatedAt, DateTime.UtcNow)
);
var result = await _products.UpdateOneAsync(p => p.Id == productId, update);
if (result.ModifiedCount > 0)
{
await RecalculateAverageRatingAsync(productId);
return true;
}
return false;
}
public async Task RecalculateAverageRatingAsync(string productId)
{
var product = await _products.Find(p => p.Id == productId).FirstOrDefaultAsync();
if (product == null || !product.Reviews.Any()) return;
var totalRating = product.Reviews.Sum(r => r.Rating);
var averageRating = Math.Round((double)totalRating / product.Reviews.Count, 1);
var update = Builders<Product>.Update.Combine(
Builders<Product>.Update.Set(p => p.Rating.Average, averageRating),
Builders<Product>.Update.Set(p => p.Rating.Count, product.Reviews.Count)
);
await _products.UpdateOneAsync(p => p.Id == productId, update);
}
public async Task<bool> DeleteProductAsync(string id)
{
// Soft delete
var update = Builders<Product>.Update.Combine(
Builders<Product>.Update.Set(p => p.IsActive, false),
Builders<Product>.Update.Set(p => p.UpdatedAt, DateTime.UtcNow)
);
var result = await _products.UpdateOneAsync(p => p.Id == id, update);
return result.ModifiedCount > 0;
}
public async Task<List<Product>> GetLowStockProductsAsync()
{
// Use aggregation pipeline to calculate available stock
var pipeline = new[]
{
new BsonDocument("$match", new BsonDocument("isActive", true)),
new BsonDocument("$addFields", new BsonDocument
{
{ "availableStock", new BsonDocument("$subtract", new BsonArray { "$inventory.quantity", "$inventory.reserved" }) }
}),
new BsonDocument("$match", new BsonDocument
{
{ "$expr", new BsonDocument("$lte", new BsonArray { "$availableStock", "$inventory.lowStockThreshold" }) }
}),
new BsonDocument("$sort", new BsonDocument("availableStock", 1))
};
return await _products.Aggregate<Product>(pipeline).ToListAsync();
}
public async Task<List<Product>> GetProductsByCategoryAsync(string category, ProductSort sort = null, Pagination pagination = null)
{
var filter = new ProductFilter { Category = category };
return await GetAllProductsAsync(filter, sort, pagination);
}
public async Task<List<Product>> GetTopRatedProductsAsync(int limit = 10)
{
return await _products.Find(p =>
p.IsActive &&
p.Rating.Count >= 5 // At least 5 reviews
)
.Sort(Builders<Product>.Sort.Descending(p => p.Rating.Average).Descending(p => p.Rating.Count))
.Limit(limit)
.ToListAsync();
}
private FilterDefinition<Product> BuildFilterDefinition(ProductFilter filter)
{
var filters = new List<FilterDefinition<Product>>();
if (!string.IsNullOrEmpty(filter.Category))
filters.Add(Builders<Product>.Filter.Eq(p => p.Category, filter.Category));
if (!string.IsNullOrEmpty(filter.Brand))
filters.Add(Builders<Product>.Filter.Eq(p => p.Brand, filter.Brand));
if (filter.MinPrice.HasValue || filter.MaxPrice.HasValue)
{
if (filter.MinPrice.HasValue && filter.MaxPrice.HasValue)
filters.Add(Builders<Product>.Filter.And(
Builders<Product>.Filter.Gte(p => p.Price, filter.MinPrice.Value),
Builders<Product>.Filter.Lte(p => p.Price, filter.MaxPrice.Value)
));
else if (filter.MinPrice.HasValue)
filters.Add(Builders<Product>.Filter.Gte(p => p.Price, filter.MinPrice.Value));
else if (filter.MaxPrice.HasValue)
filters.Add(Builders<Product>.Filter.Lte(p => p.Price, filter.MaxPrice.Value));
}
if (filter.InStock.HasValue && filter.InStock.Value)
filters.Add(Builders<Product>.Filter.Gt(p => p.Inventory.Quantity, 0));
if (filter.Tags != null && filter.Tags.Any())
filters.Add(Builders<Product>.Filter.AnyIn(p => p.Tags, filter.Tags));
return filters.Any() ? Builders<Product>.Filter.And(filters) : Builders<Product>.Filter.Empty;
}
private SortDefinition<Product> BuildSortDefinition(ProductSort sort)
{
var sortBuilder = Builders<Product>.Sort;
switch (sort.Field)
{
case "price":
return sort.Order == SortOrder.Descending
? sortBuilder.Descending(p => p.Price)
: sortBuilder.Ascending(p => p.Price);
case "rating":
return sort.Order == SortOrder.Descending
? sortBuilder.Descending(p => p.Rating.Average)
: sortBuilder.Ascending(p => p.Rating.Average);
case "name":
return sort.Order == SortOrder.Descending
? sortBuilder.Descending(p => p.Name)
: sortBuilder.Ascending(p => p.Name);
default:
return sort.Order == SortOrder.Descending
? sortBuilder.Descending(p => p.CreatedAt)
: sortBuilder.Ascending(p => p.CreatedAt);
}
}
}
// Supporting classes
public class ProductFilter
{
public string Category { get; set; }
public string Brand { get; set; }
public decimal? MinPrice { get; set; }
public decimal? MaxPrice { get; set; }
public bool? InStock { get; set; }
public List<string> Tags { get; set; }
}
public class ProductSort
{
public string Field { get; set; } = "createdAt";
public SortOrder Order { get; set; } = SortOrder.Descending;
}
public enum SortOrder
{
Ascending,
Descending
}
public class Pagination
{
public int Page { get; set; } = 1;
public int Limit { get; set; } = 20;
public int Skip => (Page - 1) * Limit;
}
public enum InventoryOperation
{
Set,
Increment,
Decrement
}
}
API Controller
using Microsoft.AspNetCore.Mvc;
using ECommerce.Services;
using ECommerce.Models;
namespace ECommerce.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly ProductService _productService;
public ProductsController(ProductService productService)
{
_productService = productService;
}
// GET: api/products
[HttpGet]
public async Task<ActionResult<List<Product>>> GetProducts(
[FromQuery] string category = null,
[FromQuery] string brand = null,
[FromQuery] decimal? minPrice = null,
[FromQuery] decimal? maxPrice = null,
[FromQuery] bool? inStock = null,
[FromQuery] string search = null,
[FromQuery] string sort = "createdAt",
[FromQuery] string order = "desc",
[FromQuery] int page = 1,
[FromQuery] int limit = 20)
{
try
{
List<Product> products;
if (!string.IsNullOrEmpty(search))
{
var filter = new ProductFilter
{
Category = category,
Brand = brand,
MinPrice = minPrice,
MaxPrice = maxPrice,
InStock = inStock
};
var pagination = new Pagination { Page = page, Limit = limit };
products = await _productService.SearchProductsAsync(search, filter, pagination);
}
else
{
var filter = new ProductFilter
{
Category = category,
Brand = brand,
MinPrice = minPrice,
MaxPrice = maxPrice,
InStock = inStock
};
var sortOptions = new ProductSort
{
Field = sort,
Order = order.ToLower() == "desc" ? SortOrder.Descending : SortOrder.Ascending
};
var pagination = new Pagination { Page = page, Limit = limit };
products = await _productService.GetAllProductsAsync(filter, sortOptions, pagination);
}
return Ok(new
{
success = true,
data = products,
pagination = new
{
page,
limit,
total = products.Count
}
});
}
catch (Exception ex)
{
return BadRequest(new { success = false, error = ex.Message });
}
}
// GET: api/products/{id}
[HttpGet("{id}")]
public async Task<ActionResult<Product>> GetProduct(string id)
{
var product = await _productService.GetProductByIdAsync(id);
if (product == null)
return NotFound(new { success = false, error = "Product not found" });
return Ok(new { success = true, data = product });
}
// POST: api/products
[HttpPost]
public async Task<ActionResult<Product>> CreateProduct(Product product)
{
try
{
var createdProduct = await _productService.CreateProductAsync(product);
return CreatedAtAction(nameof(GetProduct),
new { id = createdProduct.Id },
new { success = true, data = createdProduct });
}
catch (InvalidOperationException ex)
{
return BadRequest(new { success = false, error = ex.Message });
}
}
// PUT: api/products/{id}
[HttpPut("{id}")]
public async Task<IActionResult> UpdateProduct(string id, Product product)
{
try
{
var updated = await _productService.UpdateProductAsync(id, product);
if (!updated)
return NotFound(new { success = false, error = "Product not found" });
var updatedProduct = await _productService.GetProductByIdAsync(id);
return Ok(new { success = true, data = updatedProduct });
}
catch (InvalidOperationException ex)
{
return BadRequest(new { success = false, error = ex.Message });
}
}
// DELETE: api/products/{id}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteProduct(string id)
{
var deleted = await _productService.DeleteProductAsync(id);
if (!deleted)
return NotFound(new { success = false, error = "Product not found" });
return Ok(new { success = true, message = "Product deleted successfully" });
}
// POST: api/products/{id}/reviews
[HttpPost("{id}/reviews")]
public async Task<IActionResult> AddReview(string id, Review review)
{
var added = await _productService.AddReviewAsync(id, review);
if (!added)
return NotFound(new { success = false, error = "Product not found" });
var product = await _productService.GetProductByIdAsync(id);
return Ok(new { success = true, data = product });
}
// GET: api/products/inventory/low-stock
[HttpGet("inventory/low-stock")]
public async Task<ActionResult<List<Product>>> GetLowStockProducts()
{
var products = await _productService.GetLowStockProductsAsync();
return Ok(new { success = true, data = products });
}
// GET: api/products/top-rated
[HttpGet("top-rated")]
public async Task<ActionResult<List<Product>>> GetTopRatedProducts(int limit = 10)
{
var products = await _productService.GetTopRatedProductsAsync(limit);
return Ok(new { success = true, data = products });
}
}
}
Sample Data Seeding
// Data/SeedData.cs
using ECommerce.Services;
using ECommerce.Models;
namespace ECommerce.Data
{
public class SeedData
{
public static async Task SeedProductsAsync(ProductService productService)
{
var sampleProducts = new List<Product>
{
new Product
{
Sku = "LAPTOP-GAMING-001",
Name = "Gaming Laptop Pro",
Description = "High-performance gaming laptop with RTX 4070",
Price = 1499.99m,
Category = "electronics",
Brand = "TechCorp",
Tags = new List<string> { "gaming", "laptop", "high-performance" },
Inventory = new Inventory { Quantity = 15, LowStockThreshold = 3 },
Specifications = new Dictionary<string, string>
{
["Processor"] = "Intel Core i7-12700H",
["RAM"] = "16GB DDR5",
["Storage"] = "1TB SSD",
["Graphics"] = "NVIDIA RTX 4070",
["Display"] = "16\" QHD 165Hz"
}
},
new Product
{
Sku = "MOUSE-WIRELESS-002",
Name = "Wireless Gaming Mouse",
Description = "Ergonomic wireless mouse with RGB lighting",
Price = 79.99m,
Category = "electronics",
Brand = "GameGear",
Tags = new List<string> { "gaming", "wireless", "rgb" },
Inventory = new Inventory { Quantity = 50, LowStockThreshold = 10 },
Specifications = new Dictionary<string, string>
{
["DPI"] = "16000",
["Battery"] = "70 hours",
["Connectivity"] = "2.4GHz wireless",
["Buttons"] = "8 programmable"
}
},
new Product
{
Sku = "HEADPHONES-BT-003",
Name = "Bluetooth Headphones",
Description = "Noise-cancelling wireless headphones",
Price = 199.99m,
Category = "electronics",
Brand = "AudioMax",
Tags = new List<string> { "audio", "wireless", "noise-cancelling" },
Inventory = new Inventory { Quantity = 25, LowStockThreshold = 5 },
Specifications = new Dictionary<string, string>
{
["Battery"] = "30 hours",
["Connectivity"] = "Bluetooth 5.0",
["Features"] = "Active Noise Cancellation, Quick Charge"
}
}
};
foreach (var product in sampleProducts)
{
try
{
await productService.CreateProductAsync(product);
Console.WriteLine($"Created product: {product.Name}");
}
catch (Exception ex)
{
Console.WriteLine($"Error creating product {product.Name}: {ex.Message}");
}
}
Console.WriteLine("Seeding completed");
}
}
}
Section 4: Performance Optimization Techniques
Query Optimization
// Use projection to return only needed fields
var productNames = await products.Find(_ => true)
.Project(p => new { p.Id, p.Name, p.Price })
.ToListAsync();
// Use covered queries (index-only queries)
await products.Indexes.CreateOneAsync(
new CreateIndexModel<Product>(Builders<Product>.IndexKeys.Ascending(p => p.Category).Ascending(p => p.Price))
);
var coveredQuery = await products.Find(p => p.Category == "electronics")
.Project(Builders<Product>.Projection.Include(p => p.Category).Include(p => p.Price).Exclude(p => p.Id))
.ToListAsync();
// Analyze query performance
var explanation = await products.Find(p => p.Price > 100).ExplainAsync();
Console.WriteLine($"Execution time: {explanation.ExecutionStats.ExecutionTime.TotalMilliseconds}ms");
Console.WriteLine($"Documents examined: {explanation.ExecutionStats.TotalDocsExamined}");
Console.WriteLine($"Documents returned: {explanation.ExecutionStats.TotalDocsReturned}");
Aggregation Pipeline in C#
// Efficient aggregation for product statistics
var productStatsPipeline = new[]
{
new BsonDocument("$match", new BsonDocument("isActive", true)),
new BsonDocument("$group", new BsonDocument
{
{ "_id", "$category" },
{ "totalProducts", new BsonDocument("$sum", 1) },
{ "averagePrice", new BsonDocument("$avg", "$price") },
{ "minPrice", new BsonDocument("$min", "$price") },
{ "maxPrice", new BsonDocument("$max", "$price") },
{ "totalValue", new BsonDocument("$sum", new BsonDocument("$multiply", new BsonArray { "$price", "$inventory.quantity" })) }
}),
new BsonDocument("$sort", new BsonDocument("totalValue", -1))
};
var productStats = await products.Aggregate<BsonDocument>(productStatsPipeline).ToListAsync();
Conclusion
In this comprehensive guide, we've covered:
- Advanced querying with comparison, logical, and array operators using both LINQ and FilterDefinition
- Indexing strategies for optimal performance with the MongoDB .NET driver
- Complete CRUD operations with a real-world e-commerce example
- Product catalog management with inventory tracking and review system
- API design for product management with ASP.NET Core
- Performance optimization techniques
The e-commerce product catalog demonstrates practical MongoDB usage with complex schemas, relationships, and business logic using C# and .NET.
🚀 Next Steps:
- Implement the product catalog in your own ASP.NET Core project
- Experiment with different indexing strategies
- Add more features like shopping carts or order management
- Stay tuned for Part 3: Advanced Features and Performance Optimization
This is Part 2 of our MongoDB Mastery series. Read Part 1: Setup and Getting Started | Read Part 3: Advanced Features
Related Articles
Mastering ASP.NET Core Middleware: From Basics to Advanced (.NET 10)
A comprehensive deep dive into ASP.NET Core Middleware. Learn how the request pipeline works, how to build high-performance custom middleware, and implement real-world patterns like Multi-Tenancy, Request Logging, and API Key Authentication.
Full Stack Insights
Software Engineer
Passionate about software development, architecture, and sharing knowledge.
Quick Links
Full Stack Insights
Software Engineer
Passionate about software development, architecture, and sharing knowledge with the community.