C# & .NET

C#
C#
C#

C# & .NET Platform Architecture

A modern, type-safe, object-oriented language paired with a high-performance cross-platform runtime engine. Built for enterprise software, web microservices, game engines, and cloud-native computing.

00 / Evolution

Architectural Evolution: From Windows Framework to High-Performance Cloud Engine

Unveiled in 2000 under lead architect Anders Hejlsberg, C# was engineered to deliver the rapid productivity of high-level languages alongside the deterministic structural power of C++. Over two decades, under language leads Anders Hejlsberg and Mads Torgersen, C# and .NET transformed from a Windows-bound corporate framework into an open-source, top-tier cloud runtime competing head-to-head with Go, Rust, and Java.

Systems Roadmap 2002 → 2025+ Active LTS
.NET Framework Generics & LINQ 2002–2012 C# 1.0 → 5.0 Windows-Only CLR Roslyn & Core 1.0 Cross-Platform Rewrite 2014–2016 Open Source / MIT Linux & macOS CLI Span<T> & Unified 5 Zero-Alloc Architecture 2018–2020 Kestrel Web Speed Hardware Intrinsics Unified .NET 8 / 9 Native AOT & PGO 2023–2025+ C# 12 & 13 Syntax Microsecond Boot / Minimal APIs

Key Architectural Transitions

Roslyn Compiler API (2014)

Microsoft rewrote the C# compiler in C# itself. By exposing the compiler as an open API (Syntax Trees, Semantic Model, and Source Generators), Roslyn enabled real-time IDE diagnostics, automated refactoring, and compile-time code generation without runtime reflection.

Cross-Platform Rewrite (.NET Core)

Deconstructed the legacy Windows-only System.Web and monolithic GAC. Rebuilt modularly from the ground up to execute natively in lightweight Docker containers across Linux distributions and macOS.

Span<T> & Zero-Alloc Memory (2018)

Redesigned runtime memory representations with stack-bound interior pointers. Enabled zero-allocation string parsing, network socket buffers, and JSON pipelines without triggering Garbage Collection pauses.

Native AOT & Dynamic PGO (.NET 8/9)

Ahead-Of-Time compilation emits standalone, single-file machine binaries with instant cold-start boot (<10ms) and minimal RAM footprints. Tiered compilation leverages Profile-Guided Optimization (PGO) to re-JIT hot code paths based on live production hardware profiles.

01 / Setup

Installation & .NET SDK Setup

To develop C# applications, install the official cross-platform .NET SDK (Software Development Kit). The SDK includes the Roslyn C# compiler, the CLR runtime execution engine, and the dotnet Command-Line Interface (CLI).

Visual Studio 2022

Full-featured IDE for Windows with integrated debugging, profiler, and enterprise tools.

VS Code + C# Dev Kit

Lightweight cross-platform editor with IntelliSense, Solution Explorer, and unit test runner.

JetBrains Rider

Cross-platform .NET IDE offering deep code refactoring and performance profiling.

# 1. Verify installed .NET SDK version
dotnet --info

# 2. Create a new Console Application
dotnet new console -o UndrstandingApp

# 3. Change directory and run the application
cd UndrstandingApp
dotnet run
02 / Syntax & Anatomy

Anatomy of a C# Program: using, namespace, and class

Every C# application is constructed from fundamental building blocks: using directives to import standard libraries, namespace declarations to logically group types, and class blueprints containing methods, fields, and constructors.

1. using Directives & Implicit Usings

Imports namespaces (e.g. using System;) so types can be referenced without full qualification. Modern .NET supports project-wide global using and SDK Implicit Usings.

Modern: global using System.Text.Json;
2. File-Scoped Namespaces (C# 10+)

Eliminates nested indentation across the entire file by declaring the namespace with a semicolon.

Clean syntax: namespace UndrstandingApp;
3. Primary Constructors (C# 12+)

Declare constructor parameters directly on the class or struct signature, automatically available across all member methods without repetitive private fields.

Example: public class UserService(IDbContext db) { ... }
4. Collection Expressions (C# 12+)

Unified bracket syntax [1, 2, ..others] across arrays, List<T>, Span<T>, and immutable collections with zero-cost spread operators.

Syntax: int[] row = [10, 20, ..tail];

Modern Top-Level Syntax (C# 9+)

// Program.cs (Top-Level Statements)
// Roslyn auto-wraps namespace & Main()

using System;

Console.WriteLine("Hello, Undrstanding!");
GreetUser("Developer");

// Local function declaration
void GreetUser(string name)
{
    Console.WriteLine($"Welcome, {name}!");
}
Program.cs

Traditional Program Structure

using System; // 1. Import

namespace UndrstandingApp; // 2. Scope

public class Program // 3. Class
{
    public static void Main(string[] args) // 4. Entry
    {
        Console.WriteLine("Hello, World!");
        GreetUser("Developer");
    }

    static void GreetUser(string name) =>
        Console.WriteLine($"Welcome, {name}!");
}
Program.cs
03 / Variables

Variable Declarations, Data Types & Constants

C# is a strongly-typed language. You can declare variables explicitly with data types or implicitly using the var keyword (where the compiler infers the exact type at compile time).

Memory Subsystem 64-bit CLR Layout
Thread Call Stack (LIFO / Frame-Bound) Stack Frame: ProcessOrder() int quantity = 5 [4 bytes direct] struct Point { X=10, Y=20 } [8B inline] Order* orderRef 0x0000_7FFE_04A8 [8B Pointer] Instant deallocation on function return (0 GC pressure) Managed Heap (GC Managed) SyncBlock Index [8B Header: Lock / HashCode] MethodTable* (TypeHandle) [8B Points to EEClass/VTable] long Id = 100294 [8 bytes field] string CustomerRef ➔ [8B Pointer to String Heap] Base Overhead: 16 bytes per object (SyncBlock + MT)
Value Types (struct, int, double): Allocated directly inside their declaration context (thread stack or containing object). Zero GC overhead.
Boxing Overhead: Converting a value type to object wraps it in an envelope on the Managed Heap, incurring 16-byte object header overhead and triggering GC collections.
// Explicit Variable Declarations
int age = 25;
double score = 98.5;
decimal price = 19.99m; // High precision decimal for financial data
bool isActive = true;
string platformName = "Undrstanding";

// Implicit Typing with 'var' (Type inferred as string at compile time)
var userEmail = "student@undrstanding.com";

// Compile-Time Constant vs Readonly Field
const double Pi = 3.14159265;
readonly DateTime ServerStartTime = DateTime.UtcNow;

Console.WriteLine($"User: {userEmail} | Score: {score}");
Console.WriteLine($"Price: ${price} | Active: {isActive}");
Variables.cs
04 / Control Flow

Conditionals, Pattern Matching & Iteration Loops

Modern C# moves beyond basic if/else statements with expressive pattern matching and concise switch expressions. Iteration with foreach leverages compiler duck-typing to enumerate collections, arrays, and spans with zero heap allocation.

1. Switch Expressions & Relational Patterns

Evaluate expressions with relational comparisons (>= 200 and < 300), logical combinators (and, or, not), and discard patterns (_) in a clean, functional syntax.

Syntax: httpStatus switch { >= 200 and < 300 => "Success", _ => "Error" }
2. Property & Positional Patterns

Deconstruct object state inline without verbose null-checks. Inspect nested object properties or unpack tuples directly within branch conditions.

Syntax: user is { IsActive: true, Tier: "Pro" }
// 1. Pattern Matching with Relational & Logical Combinators
static string CategorizeStatus(int code) => code switch
{
    >= 200 and < 300 => "2xx: Success",
    >= 400 and < 500 => "4xx: Client Error",
    >= 500 and < 600 => "5xx: Server Error",
    _                  => "Unknown Status Code"
};

// 2. Duck-typed iteration with 'foreach' over collection
string[] services = ["AuthService", "OrderGateway", "PaymentWorker"];

foreach (var svc in services)
{
    Console.WriteLine($"Starting node: {svc}");
}

Console.WriteLine($"HTTP 204: {CategorizeStatus(204)}");
Console.WriteLine($"HTTP 503: {CategorizeStatus(503)}");
PatternMatching.cs
05 / Architecture

The Common Language Runtime (CLR) & Execution Engine

C# source code compiles into Common Intermediate Language (CIL/MSIL) bytecode rather than raw machine instructions. At execution time, the CLR's Just-In-Time (JIT) compiler translates bytecode into optimized native CPU instructions while managing type safety and automatic memory reclamation.

CLR Execution & GC Blueprint Roslyn ➔ RyuJIT ➔ GC Generations
EXECUTION PIPELINE: SOURCE ➔ RUNTIME NATIVE CODE C# Source (.cs) Roslyn Compiler CIL Bytecode (.dll) CLR Tiered JIT / PGO Native CPU MANAGED HEAP SEGMENTS: GENERATIONAL GARBAGE COLLECTION SOH (Small Object Heap < 85KB) Gen 0 Ephemeral / Fresh Collected in <1ms Gen 1 Survivor Buffer Short ➔ Tenured Gen 2 Tenured / Singletons Full GC Sweep LOH ≥ 85,000 B Large Buffers No Compaction POH Pinned Heap Native Sockets Zero Relocation
06 / Memory & Type System

Value Types, Reference Types, Records & Null Safety

C# cleanly segregates memory into Value Types (allocated in-place on the stack or enclosing struct) and Reference Types (managed heap allocation). Modern C# enhances this foundation with immutable records, high-performance record structs, and compile-time Nullable Reference Types.

1. Records & Non-Destructive Mutation

record class provides heap-allocated reference types with value-based equality semantics and synthesized deconstructors. Non-destructive mutation is achieved via the with expression without mutating the original instance.

Copy expression: var updated = order with { Price = 29.99m };
2. Record Structs & Nullable References

readonly record struct combines stack allocation speed (zero GC allocations) with value-based equality. Nullable Reference Types enforce static compile-time flow analysis to eradicate NullReferenceException.

Stack value: public readonly record struct Coords(double X, double Y);
// 1. Immutable Record Class with Value-Based Equality
public record UserAccount(string Id, string Email, string Role);

// 2. Zero-Allocation Stack-Allocated Record Struct
public readonly record struct GeoPoint(double Lat, double Lng);

var u1 = new UserAccount("usr_1", "numan@example.com", "Student");

// Non-destructive mutation creating a new copy with modified property
var u2 = u1 with { Role = "Lead Engineer" };

// 3. Nullable Reference Types with Flow Analysis
string? rawInput = null;
string safeName = rawInput?.Trim() ?? "Guest Developer";

Console.WriteLine($"User: {u2.Email} [{u2.Role}]");
Console.WriteLine($"Value Equality (u1 == u2): {u1 == u2}");
Console.WriteLine($"Safe User: {safeName}");
ModernTypes.cs
07 / High Performance

Span<T>, Memory<T> & Zero-Allocation Slicing

Span<T> is a stack-only ref struct representing a contiguous region of arbitrary memory (managed heap arrays, stack memory via stackalloc, or native unmanaged pointers) without allocation overhead.

Zero-Allocation Architecture ref struct Span<T> Interior Pointer
ReadOnlySpan<char> (Stack-Only ref struct) ref char _pointer ➔ 0x7FFE_04C8 int _length = 6 [Window element count] MEMORY ALLOCATION COMPARISON string.Substring(15, 6): 32B Heap Alloc + GC Gen 0 span.Slice(15, 6): 0 Bytes Allocated (1 CPU cycle) CONTIGUOUS MEMORY BUFFER (Array / Native Pointer / Stackalloc) "Authorization: " indices [0..14] "Bearer" indices [15..20] (Slice) " eyJhbGciOiJIUzI1NiIsInR5c..." indices [21..N]
// Zero-Allocation String Slicing with ReadOnlySpan<char>
string rawHeader = "Authorization: Bearer eyJhbGciOiJIUzI1Ni...";
ReadOnlySpan<char> span = rawHeader.AsSpan();

// Slice without allocating a new string object
ReadOnlySpan<char> token = span.Slice(15, 6);
Console.WriteLine($"Sliced Token: {token.ToString()}");
Console.WriteLine($"Span Length: {token.Length} chars");
SpanSlicing.cs
08 / Object Orientation

Generics, Variance & Event Architecture

Generics in .NET are fully reified by the CLR, meaning value type specialization produces dedicated machine code with zero boxing overhead. Generic variance allows controlled subtyping polymorphism across interfaces and delegates.

1. Covariance (out T) vs Contravariance (in T)

Covariance (out T) permits assigning a more derived type in read-only positions (e.g. IEnumerable<out T>). Contravariance (in T) permits assigning a less derived type in write-only argument positions (e.g. Action<in T>).

Producer/Consumer: IReadOnlyRepo<out TEntity, in TId>
2. CLR Reification & Constraints

Unlike languages with type erasure, RyuJIT generates specialized native machine code for every value type instantiation (e.g. List<int> has no boxing). Constraints (where T : unmanaged) guarantee hardware layout optimization.

Constraint: where TEntity : class where TId : notnull
// Generic Repository Interface with Type Constraints & Covariance
public interface IReadOnlyRepository<out TEntity, in TId> 
    where TEntity : class
    where TId : notnull
{
    Task<TEntity?> GetByIdAsync(TId id, CancellationToken ct = default);
}

// Strongly-Typed Event Handler Delegate
public class DataEngine
{
    public event Action<string>? OnProcessed;

    public void Execute()
    {
        OnProcessed?.Invoke("Data pipeline processing finished successfully.");
    }
}

var engine = new DataEngine();
engine.OnProcessed += msg => Console.WriteLine($"[EVENT] {msg}");
engine.Execute();
GenericsEvents.cs
09 / Functional Querying

LINQ Architecture: IEnumerable vs IQueryable & Expression Trees

Language Integrated Query (LINQ) unifies data access under declarative functional syntax. The critical architectural distinction lies in the underlying execution contract: IEnumerable<T> executes in-memory via delegates, whereas IQueryable<T> captures code as an Expression Tree (AST) that remote database providers compile into optimized SQL.

Query Engine Pipeline Delegates vs Expression Trees
IEnumerable<T> (In-Memory / Client-Side) Func<T, bool> (Compiled IL Delegate) 1. In-memory collection in RAM (List<T>, Array) 2. Evaluated on CPU via IEnumerator.MoveNext() 3. Deferred execution: Evaluates only upon enumeration Best for: Local arrays, collections, small datasets IQueryable<T> (Remote Database / Server-Side) Expression<Func<T, bool>> (AST Data Structure) 1. Query captured as Abstract Syntax Tree nodes 2. Entity Framework parses Tree ➔ Generates SQL 3. Emits: SELECT * FROM Orders WHERE Amount > 100 Prevents loading unneeded rows into application RAM
var numbers = new List<int> { 1, 2, 3, 4, 5 };

// Query defined (Deferred Execution - Not processed yet)
var query = numbers
    .Where(n => n % 2 == 0)
    .Select(n => n * 10);

numbers.Add(6); // Mutate list after query definition

// Execution triggered HERE during enumeration
foreach (var item in query)
{
    Console.WriteLine(item);
}
LinqDeferred.cs
10 / Asynchrony

Async State Machines & Thread Pool Context

Concurrency Architecture IAsyncStateMachine & IOCP ThreadPool
1. SYNC DISPATCH (Thread 1) Execute Method Body Runs synchronously until await client.GetAsync() Roslyn State Struct state = -1 (Initial) builder.AwaitUnsafeOnComp() Thread 1 returns to pool! 2. NON-BLOCKING I/O OS Kernel Driver epoll / kqueue / IOCP Zero CPU Thread Blocked Socket Receive Completed Hardware interrupt triggered 3. RESUME (Thread 14) stateMachine.MoveNext() state = 0 (Resumed) awaiter.GetResult() Task<T> Completed Result returned to caller builder.SetResult(payload) Worker Thread freed to pool
public async Task<string> DownloadPayloadAsync(string url, CancellationToken ct)
{
    using var client = new HttpClient();
    
    // Non-blocking asynchronous network request avoiding context capture
    Console.WriteLine($"[ASYNC] Dispatching request to {url}...");
    var response = await client.GetAsync(url, ct).ConfigureAwait(false);
    return await response.Content.ReadAsStringAsync(ct).ConfigureAwait(false);
}
AsyncAwait.cs
11 / Ecosystem

ASP.NET Core Architecture, Middleware Pipeline & DI Lifetimes

ASP.NET Core is a high-throughput, cloud-native web framework built on the high-performance Kestrel web server. Every incoming HTTP connection traverses a composable bidirectional middleware pipeline before resolving endpoints, utilizing the built-in Inversion-of-Control (IoC) Service Provider.

Web Pipeline Architecture Kestrel ➔ Middleware ➔ DI Scopes
KESTREL HTTP REQUEST / RESPONSE MIDDLEWARE PIPELINE (BIDIRECTIONAL) Request In (Purple ➔)  |  Response Out (Green 🠔) Kestrel HTTP/2, HTTP/3 Socket In / Out req resp ExceptionHandler Global try/catch Catches Faults next() unwind UseRouting() Route Matching Selects Endpoint next() unwind AuthN & AuthZ JWT / Claims Checks Policies exec 200 OK MapGet / Controller Executes Action Handler Generates 200 OK Request delegates advance via await next() ➔ Endpoint produces output ➔ Response unwinds back to Kestrel socket IOC CONTAINER SERVICE LIFETIMES & HTTP REQUEST SCOPE Microsoft.Extensions.DependencyInjection Singleton [Root Scope] Single shared instance across app Alive until process termination e.g. MemoryCache, HttpClientFactory Scoped [Request Scope] One instance per HTTP Request Auto-disposed at request completion e.g. AppDbContext, CurrentUserContext Transient [Per-Resolve] Created every time injected Lightweight, stateless operations e.g. Formatters, HashGenerators
// Modern ASP.NET Core Minimal API with DI Injection
var builder = WebApplication.CreateBuilder(args);

// Register Services into IoC Container
builder.Services.AddSingleton<ICacheService, MemoryCacheService>();
builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
builder.Services.AddTransient<ITransactionValidator, TransactionValidator>();

var app = builder.Build();

// Minimal API Route Endpoint with Automatic DI Parameter Binding
app.MapGet("/api/orders/{id:long}", async (long id, IOrderRepository repo, CancellationToken ct) =>
{
    var order = await repo.GetOrderAsync(id, ct);
    return order is not null ? Results.Ok(order) : Results.NotFound();
});

app.Run();
Program.cs
Architectural Guardrail: Captive Dependency

Never inject a Scoped service (like DbContext) into a Singleton service. Because the Singleton never gets disposed, the Scoped instance will be captured permanently, keeping database connections alive and causing severe concurrency exceptions.

12 / Data Access

Data Access: ADO.NET & Entity Framework Core (EF Core)

Data persistence in .NET rests upon a dual-tier architectural hierarchy: raw, low-overhead socket streaming with ADO.NET (System.Data.Common), and rich, strongly-typed domain mapping with Entity Framework Core (EF Core). Mastering high-performance .NET applications requires understanding how these tiers interact, when to bypass abstraction layers, and how to avoid costly relational pitfalls.

Data Access Pipeline & Execution Spectrum Wire Drivers ➔ Micro-ORMs ➔ Full ORM
ENTITY FRAMEWORK CORE (EF CORE) — HIGH ABSTRACTION / UNIT OF WORK PIPELINE LINQ Expression IQueryable<T> C# AST Tree Query Compiler Tree Preprocessing Emits Dialect SQL DbCommand Database Engine Streams TDS Rows Entity Materializer Instantiates POCOs Hydrates Relations Change Tracker Entity Snapshot Cache Skip via AsNoTracking() ADO.NET (SYSTEM.DATA.COMMON) — LOW-LEVEL DIRECT WIRE STREAMING PIPELINE SqlConnection Pool Recycles TCP Sockets await using var conn SqlCommand + Params sp_executesql Parameterized Prevents SQL Injection SqlDataReader (Cursor) Forward-Only Wire Stream Near-Zero Heap Allocations Direct Map / Dapper reader.GetInt32(ord) Microsecond Execution
Architecture Spectrum / ADO.NET vs. Dapper vs. EF Core
Architectural Dimension Raw ADO.NET Dapper (Micro-ORM) EF Core 8+
Abstraction Level Low-Level Wire Protocol Driver Thin Object-Mapping Wrapper Full Object-Relational Mapper (ORM)
Query Authoring Raw SQL strings & Stored Procedures Raw SQL with parameter templates Strongly-Typed LINQ & Expression Trees
Change Tracking & Unit of Work None (Manual UPDATEs) None (Manual UPDATEs) Automatic Snapshot Change Tracker & DbContext
Memory Allocation Near-Zero (forward-only streaming) Low (allocates POCO entities only) Higher (Entities + Snapshots, unless AsNoTracking)
Throughput / Latency 1.0x (Absolute Raw Speed) ~1.05x (95% of ADO.NET) ~1.15x - 1.4x (Near-Equal with AsNoTracking)
Schema Migrations Manual .sql scripts Manual .sql scripts (DbUp, Flyway) Automated Code-First Migrations (dotnet ef)
Primary Production Role Bulk ETL pipelines, binary streaming, socket control High-QPS read APIs, legacy DBs with complex SQL Enterprise domain models, rapid CRUD, complex graphs
The Low-Level Foundation / ADO.NET (System.Data.Common)

Core Architecture & The 4 Fundamental Primitives

ADO.NET is the high-performance foundational data layer of .NET. Every ORM—including Entity Framework Core and Dapper—is ultimately compiled down to ADO.NET commands and connections. It revolves around four abstract base types defined in System.Data.Common:

1. DbConnection (SqlConnection) Socket Lifecycle

Represents a physical connection to the database engine. In production, physical socket creation is expensive (~30ms TLS handshake). ADO.NET manages an internal Connection Pool: when you call Close() or Dispose(), the physical TCP connection is returned to the pool for reuse rather than severed.

Danger: Omitting await using leads to pool starvation ("Timeout expired. The timeout elapsed prior to obtaining a connection from the pool").
2. DbCommand (SqlCommand) Statement Execution

Executes SQL statements or Stored Procedures over an active connection with three fundamental execution semantics:

  • • ExecuteReaderAsync(): Streams rows via forward-only cursor.
  • • ExecuteNonQueryAsync(): Executes DML/DDL, returns affected row count.
  • • ExecuteScalarAsync(): Fetches row 1, col 1 (e.g. COUNT(*)).
3. DbDataReader (SqlDataReader) Streaming Cursor

Provides a high-performance, forward-only, read-only stream of rows directly off the network buffer. It never loads the whole dataset into RAM at once.

Optimization: Cache column ordinals (reader.GetOrdinal("Id")) outside loops and read via reader.GetInt32(ord) to avoid string hashing overhead on every row.
4. DbTransaction (SqlTransaction) ACID Atomicity

Enforces ACID guarantees across multiple commands on a single connection. Requires explicit CommitAsync() or RollbackAsync(). Supports isolation levels including ReadCommitted, Snapshot, and Serializable.

Disposing a transaction without calling commit automatically rolls it back safely.
Critical Security Rule: Parameterization vs. SQL Injection
❌ Fatal: String Interpolation var sql = $"SELECT * FROM Users WHERE Email = '{inputEmail}'";

An attacker inputs ' OR 1=1; DROP TABLE Users; --. The SQL interpreter executes the input as executable syntax, corrupting or leaking the database.

✅ Safe: Parameterized Command cmd.CommandText = "SELECT * FROM Users WHERE Email = @Email";
cmd.Parameters.Add("@Email", SqlDbType.VarChar, 100).Value = inputEmail;

Sends the SQL template and data across the wire separately via sp_executesql. The query engine reuses compiled execution plans and strictly treats the parameter as literal data.

// Production-Grade High-Performance ADO.NET Streaming Pattern
using System.Data;
using Microsoft.Data.SqlClient;

public async Task<List<CustomerDto>> GetActiveCustomersAsync(string country, CancellationToken ct)
{
    var results = new List<CustomerDto>();
    const string sql = @"SELECT CustomerId, CompanyName, CreditLimit, RegisteredUtc 
                           FROM dbo.Customers 
                           WHERE Country = @Country AND IsActive = 1;";

    // 1. Retrieve pooled connection (Recycled TCP socket)
    await using var conn = new SqlConnection(connectionString);
    await conn.OpenAsync(ct);

    // 2. Parameterized command prevents SQL injection & leverages plan cache
    await using var cmd = new SqlCommand(sql, conn);
    cmd.Parameters.Add("@Country", SqlDbType.NVarChar, 50).Value = country;

    // 3. Stream data via forward-only cursor (near-zero allocation)
    await using var reader = await cmd.ExecuteReaderAsync(CommandBehavior.SequentialAccess, ct);
    
    // 4. Cache ordinals to eliminate string hashing per-row in hot loop
    int idOrd    = reader.GetOrdinal("CustomerId");
    int nameOrd  = reader.GetOrdinal("CompanyName");
    int credOrd  = reader.GetOrdinal("CreditLimit");
    int dateOrd  = reader.GetOrdinal("RegisteredUtc");

    while (await reader.ReadAsync(ct))
    {
        results.Add(new CustomerDto(
            Id: reader.GetInt64(idOrd),
            Name: reader.GetString(nameOrd),
            CreditLimit: reader.GetDecimal(credOrd),
            RegisteredUtc: reader.GetDateTime(dateOrd)
        ));
    }

    return results;
}
ProgramAdo.cs
The Premier ORM / Entity Framework Core (EF Core)

Architecture, Change Tracker, & Query Compilation

EF Core is Microsoft's open-source, cross-platform Object-Relational Mapper. It translates strongly-typed C# LINQ queries into database dialect SQL, hydrates entity graphs, tracks property modifications, and commits transactional changes via SaveChangesAsync().

1. DbContext as Unit of Work & Thread-Safety

A DbContext instance represents a combination of the Unit of Work and Repository patterns. It coordinates connection management, model caching, and the entity state machine.

Threading Guardrail: DbContext is strictly non-thread-safe. Never execute concurrent async operations (e.g. Task.WhenAll) against the same DbContext instance. Register it as Scoped in ASP.NET Core DI so each HTTP request gets an isolated instance.
2. The Change Tracker & Entity State Machine

When an entity is queried with tracking enabled, the Change Tracker saves a snapshot of all its original scalar property values in memory. Every entity resides in one of five states:

Detached Not monitored
Unchanged Matches DB
Added Will INSERT
Modified Will UPDATE
Deleted Will DELETE

When calling await context.SaveChangesAsync(), EF Core diffs entities against original snapshots, generates targeted UPDATE statements only for modified columns, batches statements together, and executes them in a single transaction.

3. EF Core Performance Masterclass (5 Core Rules)
Rule 1: Always use .AsNoTracking() on Read Endpoints ~3x Throughput

Omits the Change Tracker snapshot creation step. Prevents memory bloat on large result sets and reduces CPU overhead. Use AsNoTrackingWithIdentityResolution() if query results contain duplicate relational entities that must share reference identity.

Rule 2: Project with .Select() to Prevent Over-Fetching Zero Unused Columns

Rather than querying whole entity objects (which fetches all 30 columns, including large NVARCHAR(MAX) audit blobs), project into a clean DTO: .Select(x => new OrderDto(x.Id, x.Total)). EF Core rewrites the emitted SQL to request only those columns.

Rule 3: Beware Cartesian Explosion — Use .AsSplitQuery() Relational Scalability

When you write .Include(o => o.Items).Include(o => o.Tags), a default single query produces a cross-product (Cartesian product) where parent data is duplicated across every joined row. Adding .AsSplitQuery() instructs EF Core to execute separate, clean SELECT statements per child table, transferring orders of magnitude less network bandwidth.

Rule 4: Avoid Lazy Loading & The N+1 Query Disaster Latency Hazard

With lazy loading proxies enabled, accessing customer.Orders inside a loop over 100 customers executes 1 + 100 = 101 separate SQL queries across the network. Always favor explicit eager loading with .Include() or direct projection.

Rule 5: Modern Bulk Operations (ExecuteUpdateAsync & ExecuteDeleteAsync) EF Core 7+ Feature

To update 1,000 entities, classic EF Core required querying all 1,000 into RAM, modifying properties, and saving back. ExecuteUpdateAsync() executes an immediate, direct UPDATE table SET ... WHERE ... statement on the database server in 1 round-trip without loading a single entity into managed memory.

// Modern EF Core 8+ Architecture: Fluent API, Split Queries & Bulk Operations
using Microsoft.EntityFrameworkCore;

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

    public DbSet<Order> Orders => Set<Order>();
    public DbSet<OrderItem> OrderItems => Set<OrderItem>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Fluent API Configuration: Keys, Indexes & Concurrency Tokens
        modelBuilder.Entity<Order>(b =>
        {
            b.HasKey(o => o.Id);
            b.Property(o => o.OrderNumber).IsRequired().HasMaxLength(32);
            b.HasIndex(o => o.OrderNumber).IsUnique();
            b.Property(o => o.TotalAmount).HasPrecision(18, 2);

            // Global Query Filter: Soft Delete pattern applied transparently
            b.HasQueryFilter(o => !o.IsDeleted);

            b.HasMany(o => o.Items)
             .WithOne(i => i.Order)
             .HasForeignKey(i => i.OrderId)
             .OnDelete(DeleteBehavior.Cascade);
        });
    }
}

// Service Layer demonstrating High-Performance EF Core Patterns
public class OrderService(AppDbContext db)
{
    // 1. High-Performance Read: AsNoTracking + AsSplitQuery avoids Cartesian blowup
    public async Task<List<OrderDto>> GetCustomerOrdersAsync(long customerId, CancellationToken ct)
    {
        return await db.Orders
            .AsNoTracking()
            .AsSplitQuery()
            .Where(o => o.CustomerId == customerId)
            .Select(o => new OrderDto(
                o.Id, 
                o.OrderNumber, 
                o.TotalAmount, 
                o.Items.Select(i => new ItemDto(i.ProductName, i.Quantity, i.UnitPrice)).ToList()
            ))
            .ToListAsync(ct);
    }

    // 2. High-Performance Bulk Update (EF Core 7+): Bypasses Change Tracker completely
    public async Task<int> ArchiveOldOrdersAsync(DateTime cutoffDate, CancellationToken ct)
    {
        return await db.Orders
            .Where(o => o.CreatedAtUtc < cutoffDate && o.Status == OrderStatus.Completed)
            .ExecuteUpdateAsync(setters => setters
                .SetProperty(o => o.Status, OrderStatus.Archived)
                .SetProperty(o => o.LastUpdatedUtc, DateTime.UtcNow), 
                ct);
    }
}
OrderContext.cs
Code-First Migrations & Safe Production Deployment

EF Core keeps relational schema in lock-step with C# domain entities through migrations. It tracks applied revisions in the database using the internal __EFMigrationsHistory metadata table:

$ dotnet ef migrations add AddCustomerCreditLimit
$ dotnet ef migrations script --idempotent --output migrate.sql
Enterprise Rule: Avoid running context.Database.Migrate() inside application startup in multi-instance Kubernetes clusters (concurrent startup causes table lock races). Always generate idempotent SQL migration scripts during CI/CD builds and apply them through automated database deployment pipelines.
Interactive Lab / Data Access Strategy & Benchmark Simulator

Simulate Latency, Allocations, & Query Plans

Select an operational workload and data access strategy below. Compare wire throughput, GC heap pressure, and generated SQL commands across the .NET data access spectrum:

Execution Time
24.1 ms
Heap Allocation
520 KB
DB Round-Trips
1 Round-Trip
Change Tracker
None (AsNoTracking)
Emitted SQL / Wire Command:
SELECT [o].[Id], [o].[OrderNumber], [o].[TotalAmount], [o].[CreatedAt] FROM [Orders] AS [o] WHERE [o].[Status] = @__status_0;
Architectural Analysis & Production Verdict:

Recommended production approach. AsNoTracking() + projection matches Dapper performance while preserving compile-time type safety.

13 / Appendix

C# & .NET Architecture Glossary

CLR (Common Language Runtime)

The high-performance virtual execution engine managing memory (GC), type safety, exception handling, thread pooling, and tiered JIT compilation.

CIL / MSIL

Common Intermediate Language bytecode emitted by the Roslyn compiler, executed cross-platform across OS architectures via RyuJIT.

Span<T> & Memory<T>

Stack-only ref struct providing type-safe contiguous memory representations over heap arrays, stack buffers, and native memory with 0 bytes allocated.

Expression Trees (IQueryable)

Abstract syntax tree representations of LINQ queries that ORM engines (like EF Core) parse and translate into optimized server-side SQL queries.

Large Object Heap (LOH & POH)

Specialized GC partitions for objects ≥ 85KB (LOH) and unmovable interop buffers (POH in .NET 5+), preventing costly memory compaction sweeps.

Native AOT & Dynamic PGO

Ahead-of-Time compilation emitting standalone CPU machine code for instant cold boot, paired with runtime Profile-Guided Optimization on hot code paths.

Roslyn Compiler API

Open-source C# compiler platform providing programmatic access to syntax trees, semantic models, code analysis, and compile-time Source Generators.

Kestrel Web Server

Cross-platform, event-driven HTTP server supporting HTTP/1.1, HTTP/2, and HTTP/3 (QUIC) optimized for cloud-native microsecond throughput.

ADO.NET (System.Data.Common)

The foundational low-level .NET data access architecture providing connection pooling (DbConnection), parameterized command execution (DbCommand), and streaming forward-only data cursors (DbDataReader).

Entity Framework Core (EF Core)

Microsoft's official open-source Object-Relational Mapper (ORM), providing strongly-typed LINQ query compilation, automated schema migrations, and Unit-of-Work transaction handling via DbContext.

Change Tracker

EF Core subsystem that tracks entity state (Unchanged, Modified, Added, Deleted, Detached) via in-memory property snapshots, automatically batching SQL updates during SaveChangesAsync().

AsNoTracking()

EF Core query modifier that bypasses Change Tracker snapshot creation and identity resolution for read-only queries, delivering ~3x throughput improvements and lower GC allocation.

Connection Pooling

Internal ADO.NET optimization that maintains an active pool of physical TCP sockets to the database server, recycling connections across requests to eliminate costly socket creation handshakes.

Split Queries (.AsSplitQuery())

EF Core loading feature that splits multi-collection .Include() queries into independent SQL statements, preventing the Cartesian product explosion caused by multiple SQL JOINs.

14 / Evaluation

Knowledge Check (12 Assessment Questions)

Verify your understanding of C# syntax, SDK installation, CLR runtime mechanics, Span memory slicing, LINQ, ASP.NET Core, ADO.NET, and EF Core architecture. Pass all 12 questions to earn your certificate.