Skip to main content

Overview

The Category model provides product classification in the supermarket system. Each category can contain multiple products through a one-to-many relationship, enabling organized inventory management and product browsing.

Class Definition

using System.ComponentModel.DataAnnotations.Schema;

namespace SupermarketWEB.Models
{
    public class Category
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string? Description { get; set; }
        public ICollection<Product>? Products { get; set; } = default!;
    }
}

Properties

Id
int
required
Primary key and unique identifier for the category. Auto-generated by the database.
Name
string
required
The category name displayed in navigation menus and product listings. Examples: “Fruits”, “Dairy”, “Beverages”, “Bakery”.
Description
string
Optional detailed description of the category. Can be used for category pages or help text. Nullable to allow categories without descriptions.
Products
ICollection<Product>
Navigation property containing the collection of products belonging to this category. Initialized to default! to satisfy non-nullable reference type requirements while allowing Entity Framework to populate it.

Relationships

Products Relationship (One-to-Many)

Each category can have multiple products:
public ICollection<Product>? Products { get; set; } = default!;
This navigation property represents the “one” side of the one-to-many relationship with Product:
  • One CategoryMany Products
  • Product contains CategoryId foreign key
  • Category contains Products collection
Loading Related Products:
// Load categories with all their products
var categories = await context.Categories
    .Include(c => c.Products)
    .ToListAsync();

foreach (var category in categories)
{
    Console.WriteLine($"{category.Name}: {category.Products?.Count ?? 0} products");
}

Usage Examples

Creating a New Category

var category = new Category
{
    Name = "Fruits",
    Description = "Fresh fruits and seasonal produce"
};

context.Categories.Add(category);
await context.SaveChangesAsync();

Creating Category with Products

var category = new Category
{
    Name = "Beverages",
    Description = "Soft drinks, juices, and water",
    Products = new List<Product>
    {
        new Product { Name = "Orange Juice", Price = 4.99m, Stock = 50 },
        new Product { Name = "Mineral Water", Price = 1.99m, Stock = 100 }
    }
};

context.Categories.Add(category);
await context.SaveChangesAsync();

Updating Category

var category = await context.Categories.FindAsync(categoryId);
if (category != null)
{
    category.Name = "Fresh Fruits";
    category.Description = "Organic and locally-sourced fresh fruits";
    await context.SaveChangesAsync();
}

Getting Categories with Product Counts

var categorySummary = await context.Categories
    .Select(c => new
    {
        c.Id,
        c.Name,
        c.Description,
        ProductCount = c.Products.Count(),
        TotalStock = c.Products.Sum(p => p.Stock),
        AveragePrice = c.Products.Average(p => p.Price)
    })
    .ToListAsync();

Deleting Category

Be careful when deleting categories. If products reference the category, you’ll need to handle the foreign key constraint.
// Option 1: Delete products first
var category = await context.Categories
    .Include(c => c.Products)
    .FirstOrDefaultAsync(c => c.Id == categoryId);

if (category != null)
{
    context.Products.RemoveRange(category.Products);
    context.Categories.Remove(category);
    await context.SaveChangesAsync();
}

// Option 2: Reassign products to another category
var productsToReassign = await context.Products
    .Where(p => p.CategoryId == oldCategoryId)
    .ToListAsync();

foreach (var product in productsToReassign)
{
    product.CategoryId = newCategoryId;
}

var category = await context.Categories.FindAsync(oldCategoryId);
context.Categories.Remove(category);
await context.SaveChangesAsync();

Database Schema

When migrated to the database, the Category table has the following structure:
ColumnTypeNullableKey
IdintNoPrimary Key (Identity)
Namenvarchar(MAX)No
Descriptionnvarchar(MAX)Yes
The Products navigation property does not create a database column. It’s a virtual collection managed by Entity Framework through the foreign key in the Products table.

Querying Patterns

Get All Categories

var categories = await context.Categories
    .OrderBy(c => c.Name)
    .ToListAsync();

Get Category by Name

var category = await context.Categories
    .FirstOrDefaultAsync(c => c.Name == "Fruits");

Get Categories with Available Products

var activeCategories = await context.Categories
    .Where(c => c.Products.Any(p => p.Stock > 0))
    .ToListAsync();

Search Categories

var searchResults = await context.Categories
    .Where(c => c.Name.Contains(searchTerm) || 
                c.Description.Contains(searchTerm))
    .ToListAsync();
The Products property uses a specific initialization pattern:
public ICollection<Product>? Products { get; set; } = default!;
Explanation:
  • ICollection<Product>? - Nullable collection type
  • = default! - Null-forgiving operator telling the compiler this will be initialized by Entity Framework
  • Allows EF to manage the collection lifecycle
  • Prevents null reference warnings while maintaining flexibility

Best Practices

  1. Unique Names: Ensure category names are unique to avoid confusion
  2. Description Usage: Provide descriptions for better user experience
  3. Eager Loading: Use .Include(c => c.Products) when you need product data
  4. Cascade Deletes: Configure cascade delete behavior in OnModelCreating if needed
  5. Null Checks: Always check if Products collection is null or empty before iterating
  6. Performance: Use projections instead of loading entire product collections when you only need counts or specific fields

Cascade Delete Configuration

To configure cascade delete behavior in SupermarketContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Category>()
        .HasMany(c => c.Products)
        .WithOne(p => p.Category)
        .OnDelete(DeleteBehavior.Cascade); // or Restrict, SetNull
}

Build docs developers (and LLMs) love