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.

FSI
Full Stack Insights
16 min read...

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

Share this article:

Related Articles

FSI

Full Stack Insights

Software Engineer

Passionate about software development, architecture, and sharing knowledge with the community.