Monday, November 17, 2025

Architecture - Unit of Work (UOW) Pattern

1️⃣ What is Unit of Work?

Unit of Work (UoW) is a design pattern that:

  • Tracks changes made during a business transaction
  • Ensures all operations either succeed together (Commit)
  • Or fail together (Rollback)
  • Avoids partial writes to the database
  • Works like a transaction manager for repositories

📌 Simple Definition

UoW ensures that multiple database operations inside a business use case are treated as one atomic unit — either fully saved or fully reverted.


 


2️⃣ Why Do We Need Unit of Work?

Problems Without UoW

  • Calling multiple repositories → each saves independently
  • Leads to partial updates, inconsistent data, and race conditions
  • No coordination between repositories

With UoW

  • All repository actions are batched
  • Saved only when Commit() is called
  • If error occurs → Rollback() ensures consistency

3️⃣ How UoW Works Internally

EF Core already implements UoW concepts:

  • DbContext tracks changes (UoW)
  • DbContext.SaveChanges() = Commit

But in DDD or layered architecture, we wrap DbContext inside a custom UoW interface.


4️⃣ Commit & Rollback Explained

👉 Commit

  • Saves all tracked changes to database
  • Calls SaveChanges() or SaveChangesAsync()
  • Ensures all operations succeed

👉 Rollback

  • Cancels the transaction
  • Discards all uncommitted changes
  • Database remains unchanged

5️⃣ Unit of Work Flow (Simple)

Start UoW ↓ Use Repositories ↓ Make Changes (Add/Update/Delete) ↓ Commit() → All changes are saved OR Rollback() → All changes discarded

6️⃣ UoW with EF Core — Code Example

IUnitOfWork.cs

public interface IUnitOfWork : IDisposable { IProductRepository Products { get; } IOrderRepository Orders { get; } Task<int> CommitAsync(); // Commit = Save void Rollback(); // Rollback = Discard changes }

UnitOfWork.cs

public class UnitOfWork : IUnitOfWork { private readonly AppDbContext _context; public UnitOfWork(AppDbContext context) { _context = context; Products = new ProductRepository(context); Orders = new OrderRepository(context); } public IProductRepository Products { get; private set; } public IOrderRepository Orders { get; private set; } public async Task<int> CommitAsync() { return await _context.SaveChangesAsync(); } public void Rollback() { // Discard changes tracked by DbContext foreach (var entry in _context.ChangeTracker.Entries()) { switch (entry.State) { case EntityState.Modified: case EntityState.Deleted: case EntityState.Added: entry.State = EntityState.Detached; break; } } } public void Dispose() { _context.Dispose(); } }

7️⃣ Using Unit of Work — Example

Example Business Use Case: PlaceOrder

public async Task PlaceOrder(Order order) { using var uow = _unitOfWork; try { uow.Orders.Add(order); uow.Products.UpdateStock(order.ProductId, -order.Quantity); await uow.CommitAsync(); // Commit all } catch (Exception) { uow.Rollback(); // Discard changes throw; } }

✔ Both repositories share the same DbContext

✔ UoW ensures either:

  • Order saved + Stock updated → together
  • Or nothing saved → Rollback

8️⃣ Interview-Friendly Summary

Repository

  • Provides an abstraction over data logic
  • Works per entity (ProductRepository, OrderRepository)

Generic Repository

  • Provides base CRUD logic for all entities
  • Reduces duplication

Unit of Work

  • Controls multiple repositories
  • One transaction for the whole business operation
  • Offers Commit() and Rollback()
  • Maintains consistency

Architecture - Repository & Generic Repository Patterns

 ✅ 1. Repository Pattern

What it is

A Repository acts as an abstraction layer between your domain/business logic and the data access layer.
Instead of writing EF Core queries directly in controllers/services, you wrap them inside repository classes.

Why we use it

  • Keeps business logic independent from persistence logic

  • Easy to mock for unit testing

  • Central place to maintain DB queries


Example: Product Repository (Non-Generic)

Entity

public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } }

IProductRepository

public interface IProductRepository { Task<Product> GetByIdAsync(int id); Task<IEnumerable<Product>> GetAllAsync(); Task AddAsync(Product product); void Update(Product product); void Delete(Product product); }

Implementation

public class ProductRepository : IProductRepository { private readonly AppDbContext _context; public ProductRepository(AppDbContext context) { _context = context; } public async Task<Product> GetByIdAsync(int id) => await _context.Products.FindAsync(id); public async Task<IEnumerable<Product>> GetAllAsync() => await _context.Products.ToListAsync(); public async Task AddAsync(Product product) => await _context.Products.AddAsync(product); public void Update(Product product) => _context.Products.Update(product); public void Delete(Product product) => _context.Products.Remove(product); }

👉 This is a repository dedicated only to Product.


2. Generic Repository Pattern

What it is

A Generic Repository provides a reusable base class for CRUD operations.
Instead of writing a repository per entity, you write one that works with all entities.

Why we use it

  • Avoids duplicated CRUD code

  • Cleaner structure

  • Extensible for entity-specific repositories


IGenericRepository

public interface IGenericRepository<T> where T : class { Task<T> GetByIdAsync(int id); Task<IEnumerable<T>> GetAllAsync(); Task AddAsync(T entity); void Update(T entity); void Delete(T entity); }

Implementation

public class GenericRepository<T> : IGenericRepository<T> where T : class { protected readonly AppDbContext _context; public GenericRepository(AppDbContext context) { _context = context; } public async Task<T> GetByIdAsync(int id) => await _context.Set<T>().FindAsync(id); public async Task<IEnumerable<T>> GetAllAsync>() => await _context.Set<T>().ToListAsync(); public async Task AddAsync(T entity) => await _context.Set<T>().AddAsync(entity); public void Update(T entity) => _context.Set<T>().Update(entity); public void Delete(T entity) => _context.Set<T>().Remove(entity); }

🔥 Entity-Specific Repository + Generic Repository

Sometimes we extend generic repository for custom queries:

public interface IProductRepository : IGenericRepository<Product> { Task<IEnumerable<Product>> GetProductsAbovePrice(decimal price); }
public class ProductRepository : GenericRepository<Product>, IProductRepository { public ProductRepository(AppDbContext context) : base(context) { } public async Task<IEnumerable<Product>> GetProductsAbovePrice(decimal price) { return await _context.Products .Where(p => p.Price > price) .ToListAsync(); } }

Sunday, November 16, 2025

EF - N+1 Problem

 ✅ What is the N+1 Query Problem?

The N+1 problem occurs when your code runs:

  • 1 query to load a list of parent records

  • + N additional queries to load related data for each item individually

So total queries = 1 + N
This is hugely inefficient.


🟦 Simple Example (Best way to understand)

Suppose you want to load a list of Employees and their Projects.

You write:

var employees = context.Employees.ToList(); foreach (var emp in employees) { Console.WriteLine(emp.Projects.Count); }

❌ What actually happens with Lazy Loading:

Query 1:

SELECT * FROM Employees;

Now assume Employees table has 10 records.

When we loop through them, EF loads Projects for each employee:

Query 2:

SELECT * FROM Projects WHERE EmployeeId = 1;

Query 3:

SELECT * FROM Projects WHERE EmployeeId = 2;

Query 4:

SELECT * FROM Projects WHERE EmployeeId = 3;

… and so on 10 times.


✔ Total queries = 1 (main query) + 10 (project queries) = 11 queries

This is the N+1 problem.


🟥 Why is this bad?

  • Causes too many database round-trips

  • Slows applications massively

  • Wastes network + DB CPU

  • Very harmful in Web APIs, where response time matters


🟩 How to Fix the N+1 Problem? (Eager Load)

Use .Include():

var employees = context.Employees .Include(e => e.Projects) .ToList();

✔ SQL Generated (Single JOIN Query):

SELECT * FROM Employees LEFT JOIN Projects ON Projects.EmployeeId = Employees.Id;

✔ Total queries = 1 query only

No matter how many employees exist.


🟧 Interview Answer (Best Version)

The N+1 problem happens when the application fires one query to fetch the main data and then fires N additional queries to load related data for each item. This usually happens with lazy loading.

For example, loading 100 employees and then loading each employee’s projects separately results in 101 queries.

The solution is using eager loading with .Include() to fetch all related data in a single query.


🟦 Analogy (Very Easy to Remember)

Imagine you want 10 grocery items.

❌ Lazy Loading Way (N+1)

  • You go to the store (1 trip)

  • Then you go back 10 separate times to buy each item (10 trips)

Total trips = 11

✔ Eager Loading Way

  • You go once and buy all 10 items together

Total trips = 1

EF - Entity Framework Core

 ✅ 1. What is Entity Framework Core? (Interview-ready definition)

EF Core is Microsoft’s Object-Relational Mapper (ORM) that lets you interact with a database using .NET classes instead of SQL queries.
It handles:

  • Database Connection & Commands

  • Mapping classes ↔ tables

  • CRUD operations

  • Migrations

  • Change Tracking

  • LINQ queries

Interview one-liner:

EF Core is an ORM that allows developers to work with databases using C# objects, eliminating most SQL and improving productivity while maintaining performance.


2. EF Core Workflow (Very Important for Interviews)

Model (C# classes) ↓ DbContext (Mapping + Configuration) ↓ Database (Migrations) ↓ LINQ Queries (CRUD)

3. Hands-On Practical Example (Minimal, Perfect for Learning)

Let’s create a simple Employee Management app with EF Core.


🟦 Step 1: Install EF Core Packages

Run in terminal:

dotnet add package Microsoft.EntityFrameworkCore dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package Microsoft.EntityFrameworkCore.Tools

🟦 Step 2: Create Model Class

public class Employee { public int Id { get; set; } public string Name { get; set; } public string Department { get; set; } public decimal Salary { get; set; } }

🟦 Step 3: Create DbContext

using Microsoft.EntityFrameworkCore; public class AppDbContext : DbContext { public DbSet<Employee> Employees { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder options) { options.UseSqlServer("Server=.;Database=EFCoreDemo;Trusted_Connection=True;"); } }

🟦 Step 4: Create Database Using Migrations

dotnet ef migrations add InitialCreate dotnet ef database update

This creates your database + Employees table.

In interviews, they often ask:
What are migrations?
Answer:
Migrations help you evolve your database schema over time while keeping data safe.


🟦 Step 5: Perform CRUD Operations


Insert Data

using var context = new AppDbContext(); var emp = new Employee { Name = "John", Department = "IT", Salary = 50000 }; context.Employees.Add(emp); context.SaveChanges();

Read Data (LINQ)

var employees = context.Employees.ToList();

Update

var emp = context.Employees.First(); emp.Salary = 60000; context.SaveChanges();

Delete

var emp = context.Employees.First(); context.Employees.Remove(emp); context.SaveChanges();

4. Frequently Asked EF Core Interview Questions (with answers)

1. What is DbContext?

DbContext is the main class that manages database connections, CRUD operations, and mapping between classes and tables.


2. What is DbSet?

DbSet represents a table. It lets you query and save instances of a model.


3. What is Change Tracking?

EF Core automatically tracks object changes and updates only modified fields.


4. What are Migrations?

Migrations manage schema changes without dropping the database.


5. What is Lazy Loading, Eager Loading, Explicit Loading?

  • Eager Loading: Includes related data immediately using .Include()

  • Lazy Loading: Related data is loaded automatically when accessed

  • Explicit Loading: Manually load related data using Entry(entity).Collection().Load()


6. How to use LINQ with EF Core?

Example:

var itEmployees = context.Employees .Where(e => e.Department == "IT") .ToList();

5. Relationship Example (Highly asked in interviews)

Example: One-to-Many

Employee → Projects
One employee can have many projects.


Models:

public class Employee { public int Id { get; set; } public string Name { get; set; } public List<Project> Projects { get; set; } } public class Project { public int Id { get; set; } public string Title { get; set; } public int EmployeeId { get; set; } public Employee Employee { get; set; } }

DbContext Mapping:

public DbSet<Employee> Employees { get; set; } public DbSet<Project> Projects { get; set; }

Query with include:

var employees = context.Employees .Include(e => e.Projects) .ToList();

EF - Lazy Vs Eager loading

What is Lazy Loading?

Lazy loading loads related data automatically when it's accessed, not when the main entity is loaded. It can cause multiple SQL queries.

What is Eager Loading?

Eager loading loads all required related data in a single query using .Include(), improving performance in most web applications.

When should you avoid lazy loading?

Avoid lazy loading in Web APIs because it can produce many unnecessary queries and severely impact performance.

 



Sunday, November 9, 2025

Dotnet Core - Response Caching vs Output Caching

 ⚖️ Response Caching vs Output Caching in ASP.NET Core

AspectResponse CachingOutput Caching
PurposeInstructs clients (like browsers) and proxies to cache responsesCaches the actual server-generated response in memory (on the server)
Caching LocationClient-side (browser, CDN, proxy)Server-side (in ASP.NET Core memory or distributed cache)
ImplementationUses HTTP Cache-Control headers and middlewareUses the new OutputCache Middleware (ASP.NET Core 7+)
How it WorksAdds headers like Cache-Control, Vary, etc. → tells browser/CDN when to reuse responsesMiddleware saves response output → reuses for identical requests without re-running controller/action
ScopeWorks outside server (helps client cache responses)Works inside server (bypasses controller execution)
Configuration Examplecsharp\n[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Client)]\ncsharp\napp.UseOutputCache();\n[OutputCache(Duration = 60)]\n
DependenciesRelies on clients or CDNs honoring headersFully managed by ASP.NET Core runtime
Use CaseStatic or semi-static responses (e.g., images, GET APIs)Heavy computation APIs, views, or controller actions with repeated requests
Introduced in.NET Core 1.0+.NET 7.0+
InvalidationControlled by headers (no-cache, max-age)Controlled programmatically (OutputCacheStore can evict or tag invalidations)


🧩 Example Scenarios

  • Response Caching

    • Browser caches a product list API for 60 seconds.

    • CDN serves cached version — no new request hits your API.

    • Great for read-heavy GET endpoints.

  • Output Caching

    • Server caches rendered HTML for /home/index or JSON for /api/dashboard.

    • Subsequent requests return cached result from memory — controller not even called.

    • Great for high CPU or DB-intensive endpoints.


🔧 Combined Use

You can combine both:

  • Server caches the response using Output Cache.

  • Sends ResponseCache headers to help client/CDN reuse it.

This provides multi-level caching — server + client.

Dotnet Core - Performance Tuning of Applications

 🚀 How to Improve Performance in ASP.NET Core Applications

Performance optimization in .NET Core is multi-layered, covering areas from code-level tuning to infrastructure scaling.
Here’s a structured breakdown:





🧱 1. Use Built-in Caching

🔹 Memory Cache (for small-scale, single-server)

builder.Services.AddMemoryCache(); public class ProductService { private readonly IMemoryCache _cache; public ProductService(IMemoryCache cache) => _cache = cache; public Product GetProduct(int id) { return _cache.GetOrCreate($"product_{id}", entry => { entry.SlidingExpiration = TimeSpan.FromMinutes(5); return LoadProductFromDb(id); }); } }

🔹 Distributed Cache (for load-balanced apps)

Use Redis, SQL Server, or AWS ElastiCache.

builder.Services.AddStackExchangeRedisCache(options => { options.Configuration = "localhost:6379"; });

✅ Benefit: Reduces DB hits and speeds up responses.


⚙️ 2. Enable Response Caching

Cache HTTP responses for static or infrequently changing data.

builder.Services.AddResponseCaching(); app.UseResponseCaching();
[ResponseCache(Duration = 60)] public IActionResult GetWeather() => Ok("Sunny");

✅ Benefit: Reuses responses instead of regenerating data.


⚡ 3. Use Asynchronous Code

ASP.NET Core is non-blocking. Always use async/await for I/O operations:

public async Task<IActionResult> GetProductsAsync() => Ok(await _db.Products.ToListAsync());

✅ Benefit: Frees up threads → handles more concurrent requests.


🧰 4. Optimize Entity Framework Core

  • Use AsNoTracking() for read-only queries.
  • Fetch only needed columns via Select().
  • Use compiled queries for hot paths.
  • Use connection pooling.

Example:

var products = await _context.Products .AsNoTracking() .Select(p => new { p.Id, p.Name }) .ToListAsync();

✅ Benefit: Reduces EF overhead and memory allocations.


🧩 5. Compression & Minification

Enable Response Compression middleware:

builder.Services.AddResponseCompression(); app.UseResponseCompression();

Compresses JSON, HTML, CSS, etc., reducing network load.


💾 6. Use Data Transfer Optimization

  • Use DTOs to avoid over fetching large models.
  • Paginate results.
  • Use gzip/brotli compression and HTTP/2.

🌐 7. Use CDN & Static File Optimization

Host static assets (JS, CSS, images) on a CDN like CloudFront.
Also, use cache headers:

app.UseStaticFiles(new StaticFileOptions { OnPrepareResponse = ctx => { ctx.Context.Response.Headers.Append("Cache-Control", "public,max-age=604800"); } });

🧮 8. Connection & Pooling

Use IHttpClientFactory for efficient, pooled HTTP calls:

builder.Services.AddHttpClient("MyClient");

Avoids socket exhaustion caused by frequent new HttpClient instances.


🧠 9. Reduce Middleware Overhead

Register only necessary middleware, and put frequently used ones first (e.g., authentication before routing).


🧩 10. Use Output Caching (ASP.NET Core 7+)

Output caching stores full HTTP responses:

builder.Services.AddOutputCache(); app.UseOutputCache();
[OutputCache(Duration = 60)] public IActionResult GetProducts() => Ok(Products);

✅ Much faster than recomputing each request.


☁️ 11. Use Cloud & Deployment Optimization (AWS Context)

On AWS, you can:

  • Use Elastic Beanstalk or ECS/EKS for autoscaling.
  • Add AWS ElastiCache for distributed caching.
  • Use AWS CloudFront CDN.
  • Add Application Load Balancer for traffic balancing.
  • Store assets in S3 instead of app server.

✅ You can also enable AOT (Ahead-of-Time Compilation) for startup performance improvements.


🧪 12. Profiling & Diagnostics

Use tools like:

  • dotnet-trace, dotnet-counters
  • Application Insights
  • MiniProfiler
  • AWS X-Ray (for distributed tracing)

✅ Identify slow methods, DB queries, or excessive GC pressure.


🧠 13. Memory Management

  • Use Span<T>, Memory<T> for high-performance code.
  • Avoid large object allocations.
  • Prefer pooled objects (ArrayPool<T>).
  • Release disposable resources properly.

✅ Summary Table

AreaTechniqueBenefit
CachingMemory, Redis, Output CacheReduce recomputation
Async I/Oasync/awaitHandle more requests
EF CoreAsNoTracking, ProjectionOptimize DB performance
CompressionGzip, BrotliReduce payload
MiddlewareOptimize pipelineReduce latency
CDN/StaticCache headersReduce load
DiagnosticsProfilers, LogsIdentify bottlenecks

💡Summary (Short Answer)

"In ASP.NET Core, I improve performance using multi-level caching (in-memory, distributed, and output), async programming, optimized EF Core queries, response compression, and CDN-based static content delivery. I also use IHttpClientFactory for efficient HTTP calls, limit middleware overhead, and profile apps with Application Insights or AWS X-Ray to find hot spots. In cloud deployments, I leverage autoscaling and caching via AWS ElastiCache and CloudFront."

Architecture - CQRS (Command Query Responsibility Segregation)

 🧩 1. What is CQRS?

CQRS stands for:

Command
Query
Responsibility
Segregation

It means:

Separate the read (Query) and write (Command) operations of your application into different models.

 


Problem with traditional architectures and how CQRS pattern solves it-

Traditional architectures often face challenges in handling high loads and managing complex data requirements. In these systems, the same model is used for both reading (fetching data) and writing (updating data), which can lead to performance issues. As the application grows, handling large read and write requests together becomes harder, creating bottlenecks and slowing down responses.

CQRS solves this by separating read and write operations into distinct models.

  • This means write requests (commands) and read requests (queries) are processed independently, optimizing each for its specific task.
  • As a result, CQRS allows systems to handle higher traffic efficiently, improves performance, and simplifies scaling by allowing independent optimization of read and write parts.


How to Sync Databases with CQRS Design Pattern -
Synchronizing databases in a system that follows the CQRS pattern can be challenging due to the separation of the write and read sides of the application.

How-to-Sync-Databases-with-CQRS


Here is how you can handle the synchronization:

  1. Write to the Command Database: When you make changes (create, update, delete), they are first saved in the command database. This database is optimized for handling write operations.
  2. Generate Events: After the write operation is successful, the system generates events that describe what changed (like "Order Created" or "User Updated"). These events serve as notifications about the updates.
  3. Update the Query Database: The read database, optimized for fast queries, listens for these events and applies the changes to its own copy of the data. This way, the query database gets updated with the latest information.
  4. Eventual Consistency: The key idea is that the query database doesn’t have to update immediately. There can be a slight delay, but eventually, both databases will sync, ensuring consistency over time.


Challenges of using CQRS Design Pattern

Below are the challenges of using CQRS Design Pattern:
  • Complexity: Your system may become more complex if you use CQRS, particularly if you are unfamiliar with the pattern. It can be difficult to coordinate data synchronization, manage distinct read and write models, and guarantee consistency between the two.
  • Consistency: Maintaining consistency between the read and write models can be challenging, especially in distributed systems where data updates may not propagate immediately. Careful planning and execution are necessary to guarantee stability over time without compromising scalability or performance.
  • Data Synchronization: It might be difficult to keep the read and write models in sync, particularly when handling complicated data transformations or big data sets. It can be beneficial to use strategies like message queues or event sources.
  • Performance Overhead: Implementing CQRS can introduce performance overhead, especially if not done carefully. For example, using event sourcing for the write model can impact write performance, while keeping the read model updated in real-time can impact read performance.
  • Operational Complexity: Operational complexity may rise while managing two databases or data storage (one for read and one for write). This covers duties including monitoring, backup and restoration, and guaranteeing data durability and high availability.

Best Practices for implementing CQRS pattern

Below are some of the best practices for implementing CQRS pattern:

Separate Read and Write Models Carefully:
Clearly divide the system into models for reading data (queries) and writing data (commands). This separation helps keep each model simple and optimized for its specific task.

Use Asynchronous Communication When Needed:
Since commands and queries are separated, consider using asynchronous messaging for commands. This helps the system stay responsive and handle high traffic efficiently, even if some operations take longer.

Keep Commands and Queries as Simple as Possible:
Design commands to focus only on changing data (like “CreateOrder” or “UpdateUser”) and queries only on retrieving data (like “GetOrderDetails”). Avoid mixing read and write logic in either part to keep things clean and maintainable.

Embrace Event Sourcing for Data Consistency:
Event sourcing can be paired with CQRS to keep a record of all changes. Each change is saved as an event, and the current state is rebuilt from these events. This can make it easier to track history, recover data, or audit changes.

Consider the Complexity of Your System:
CQRS adds some complexity, so it’s best suited for systems with high read and write demands or complex business rules. For simpler systems, CQRS might be overkill and add unnecessary development overhead.


⚙️ Without CQRS (Traditional CRUD)

In a typical system, we use a single model (e.g., Product) for everything:

public class ProductController : Controller { private readonly AppDbContext _context; public ProductController(AppDbContext context) { _context = context; } public IActionResult Get(int id) => Ok(_context.Products.Find(id)); public IActionResult Create(Product product)     {
        _context.Add(product); _context.SaveChanges(); return Ok();
    } }

🔸 Both read and write share the same entity (Product).
🔸 Works fine for small systems, but becomes hard to optimize and scale.


⚙️ With CQRS

We split the system into two logical parts:

TypeResponsibilityTypical OperationExample
CommandChanges system state (writes)Create / Update / DeleteCreateOrder, UpdateUser
QueryReads data onlyRead / GetGetOrders, GetUserById

➡️ Each side can have its own model, own data access, even separate databases.


🧠 2. Why CQRS?

BenefitDescription
ScalabilityYou can scale reads and writes independently.
PerformanceReads can use denormalized or cached data for speed.
Simplicity per sideEach side (Command/Query) is simpler and more focused.
MaintainabilityEasier to evolve business logic (especially in DDD).
Event-driven integrationWorks great with Event Sourcing and Message Queues.

🧩 3. CQRS in .NET Example

Using MediatR (popular CQRS library for .NET):

Command:

public record CreateOrderCommand(string ProductName, int Quantity) : IRequest<int>;

Command Handler:

public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, int> { private readonly AppDbContext _context; public CreateOrderHandler(AppDbContext context) => _context = context; public async Task<int> Handle(CreateOrderCommand request, CancellationToken cancellationToken) { var order = new Order { ProductName = request.ProductName, Quantity = request.Quantity }; _context.Orders.Add(order); await _context.SaveChangesAsync(); return order.Id; } }

Query:

public record GetOrderByIdQuery(int Id) : IRequest<Order>;

Query Handler:

public class GetOrderByIdHandler : IRequestHandler<GetOrderByIdQuery, Order> { private readonly AppDbContext _context; public GetOrderByIdHandler(AppDbContext context) => _context = context; public async Task<Order> Handle(GetOrderByIdQuery request, CancellationToken cancellationToken) { return await _context.Orders.FindAsync(request.Id); } }

Controller:

public class OrderController : ControllerBase { private readonly IMediator _mediator; public OrderController(IMediator mediator) => _mediator = mediator; [HttpPost] public async Task<IActionResult> Create(CreateOrderCommand command) => Ok(await _mediator.Send(command)); [HttpGet("{id}")] public async Task<IActionResult> Get(int id) => Ok(await _mediator.Send(new GetOrderByIdQuery(id))); }

🧭 4. CQRS + Event Sourcing (Advanced)

Often used together:

  • Command side emits Events (e.g., OrderCreatedEvent)
  • Query side listens to those events and updates read models (denormalized views or cache).

So data flows asynchronously, improving performance and scalability.


🧠 5. When to Use (and When Not)

Use CQRS WhenAvoid CQRS When
Complex domain logicSimple CRUD apps
High read/write loadSmall internal tools
Need for scalabilityShort-lived prototypes
Event-driven architectureTraditional monolith

📊 6. Summary

AspectCommand SideQuery Side
PurposeModify dataRead data
ModelRich domain modelDTOs / View Models
DBOLTP (normalized)Read DB / Cache
ExampleCreateOrderCommandGetOrdersQuery


CI/CD - Safe DB Changes/Migrations

Safe DB Migrations means updating your database schema without breaking the running application and without downtime . In real systems (A...