A SaveChangesInterceptor gives you one place to capture every insert, update, and delete: the acting user, the old and new values, and the affected columns. This guide builds a complete audit log with EF Core interceptors and covers the traps: sensitive data in the payload, unbounded growth, and bulk updates that bypass the interceptor entirely.
DbContext configuration decides how your app behaves under load: service lifetime, tracking defaults, pooling, retries, and interceptors. This guide covers the settings that matter, the mistakes that cause concurrency bugs, and when context pooling is actually worth it.
Temporal tables automatically track the full history of every row. EF Core 6+ has built-in support for configuring and querying temporal tables on SQL Server.
PostgreSQL gives you row locks, SKIP LOCKED job queues, and advisory locks that most .NET developers never touch. Here is how to use each one from EF Core, with transaction patterns that avoid deadlocks.
The repository pattern is one of the most debated patterns in .NET. This guide shows a clean EF Core implementation, explains why generic repositories backfire, and tells you exactly when to skip the pattern entirely.
EF Core DbContext already implements the Unit of Work pattern. But sometimes you need an explicit abstraction. Here is when and how to implement IUnitOfWork with EF Core.
DeleteBehavior in EF Core controls two different things that people conflate: what EF does to tracked entities in memory, and what the database enforces with ON DELETE. The two can disagree, and when they do you get deletes that work in tests and fail in production. Here is the full map.
Identity columns are the default, but they only hand you the id after the insert. HiLo assigns ids before SaveChanges, which unlocks setting foreign keys on unsaved object graphs and leaner batch inserts. Here is how all three strategies work and how to pick.
Two users edit the same row, and the last write silently wins. PostgreSQL already versions every row internally through the xmin system column, and EF Core can use it as a concurrency token with zero schema changes. Here is how to map it, handle the conflict, and the caveats you should know before shipping it.
Derived values calculated in C# drift out of sync the moment someone updates the database directly. Computed columns push the calculation into the database itself, so the value is always consistent. Here is how to map them in EF Core, and why the stored vs virtual choice decides whether you can index them.
Fifty entity configurations all setting the same string length and decimal precision is not configuration, it is copy-paste. EF Core lets you define model-wide conventions once with ConfigureConventions, and write your own convention classes for anything it does not cover.
Categories with subcategories, org charts, comment threads: hierarchies are everywhere, and the obvious self-referencing entity is easy to write and brutal to query recursively. Here is how to model adjacency lists in EF Core, load whole trees without N+1 queries, and when to reach for recursive CTEs or Postgres ltree instead.
Composite primary keys look like a simple HasKey call, but they change how Find works, how relationships are configured, and how your indexes behave. Here is how to configure them correctly and when a surrogate key is the better choice.
EF Core stores enums as ints by default, which breaks the day someone reorders the enum. Storing them as strings survives refactoring, but it silently changes how ORDER BY and comparisons translate to SQL. Here are all three options, including native PostgreSQL enums, and when each one wins.
Some data does not deserve its own table: settings blobs, address snapshots, flexible metadata. JSON columns give you document flexibility inside a relational row, and with ToJson in EF Core you keep LINQ querying into the JSON. Here is how to map, query, update, and index them.
Shadow properties exist in the EF Core model but not in your entity classes. They are useful for audit fields, foreign keys, and metadata you want in the database but not in your domain model.
TPH, TPT, and TPC map inheritance to different table and join shapes. Compare their constraints, generated SQL, and performance trade-offs before choosing a strategy.
Value conversions translate between a domain type and its database column: enums stored as strings, strongly typed IDs stored as Guids, value objects stored as scalars. Here is how to use HasConversion, when you need a ValueComparer, and where converted properties break LINQ translation.
Complex types map value objects with no identity into the owner's table. In EF Core 8 they were always required, and EF Core 10 added optional complex properties plus JSON mapping with collection support. Here is how to configure, query, and update them.
One-to-one, one-to-many, many-to-many - EF Core supports them all. Here is how to configure entity relationships properly with Fluent API and conventions.
Cloud databases drop connections. EF Core's built-in retry logic handles transient failures automatically. Here is how to configure it and avoid common pitfalls.
In most applications a single database is enough. But when you need to split data across databases - for scaling, multi-tenancy, or module isolation - EF Core supports it cleanly with multiple DbContexts.
EF Core offers several approaches for seeding data - from HasData in model configuration to custom initialization logic and SQL scripts. Here is when to use each strategy.
Calling Database.Migrate at startup means your schema changes run when the app boots, with the app pool racing itself and failures taking production down. Migration bundles package migrations into a self-contained executable that runs as a pipeline step, so a bad migration fails the deploy, not the app.
Rolling back an EF Core migration is two commands, but only in the right order. Delete the migration file first and EF Core loses the ability to generate the Down SQL, leaving your database and model out of sync. Here is the safe sequence for local, shared, and production databases.
Deploying database changes without downtime requires careful planning. The expand-contract pattern, additive-only migrations, and backwards-compatible changes make zero-downtime deployments possible with EF Core.
PostgreSQL is a powerful open-source database with features like JSONB, arrays, and full-text search. Here is how to set it up with EF Core using the Npgsql provider.
EF Core migrations are easy in development but tricky in production. Here are best practices for managing schema changes safely: idempotent scripts, migration bundles, CI/CD pipelines, and avoiding common pitfalls.
Npgsql throws "Cannot write DateTime with Kind=Unspecified to PostgreSQL type timestamp with time zone" the first time a non-UTC DateTime reaches the database. The legacy switch makes it go away and your timestamps wrong. Normalizing Kind at the boundary makes both go away.
Large DbContexts pay a model-building tax on first use, and it grows with every entity you add. Compiled models move that work to build time and can cut cold start by an order of magnitude. They also come with real restrictions, including a hard conflict with global query filters, that decide who should use them.
The "instance of entity type cannot be tracked because another instance with the same key value is already being tracked" error means two objects with the same key collided inside one DbContext. Sprinkling AsNoTracking hides it. Resolving through the change tracker fixes it.
EF Core 9 throws "The model for context has pending changes" when your model no longer matches the last migration. Sometimes you really did forget a migration. But the sneaky trigger is dynamic values in HasData seed data, where every model build looks like a new change. Here is how to diagnose and fix both.
AsNoTracking is the standard advice for read-only EF Core queries, but it has a side effect few people notice: shared related entities get duplicated in memory, one copy per parent row. AsNoTrackingWithIdentityResolution fixes that for a small CPU cost. Here is when each one wins.
AddDbContextPool can shave allocations off every request by recycling DbContext instances instead of creating them. But a pooled context is a reused object, and any state you stash on it silently leaks into the next request. Here is when pooling pays off and how to avoid its traps.
FindAsync and FirstOrDefaultAsync look interchangeable for loading by primary key, but they behave differently in one crucial way: Find checks the change tracker before touching the database. That is either a free cache hit or a stale-data surprise, depending on your code.
Generated SQL exposes inefficient joins, repeated round trips, and missing predicates hidden by LINQ. Inspect it with EF Core logging, query tags, interceptors, and profiling tools.
The Change Tracker is at the heart of EF Core. It tracks every entity you load or add, figures out what changed, and generates the right SQL. Here is how it works.
EF Core gives you three ways to load related data: eager loading, explicit loading, and lazy loading. Each has trade-offs. Picking the right one can mean the difference between a fast query and the N+1 problem.
EF Core is powerful, but it is easy to write LINQ queries that generate slow SQL. Here are the most common performance mistakes and how to fix them: N+1 queries, over-fetching, missing indexes, and more.
The N+1 query problem is the most common EF Core performance killer. One query loads the parents, then N queries load the children. Here is how to detect and fix it.
Most EF Core slowdowns come from query shape, excess tracking, or round trips. Choose projections, split queries, batching, compiled models, and diagnostics.
EF Core and Dapper are the two most popular .NET data access libraries. EF Core provides a full ORM with change tracking, migrations, and LINQ. Dapper gives you raw SQL performance with minimal abstraction. Here is when to use each one.
Authentication is infrastructure, authorization is a business rule, and confusing the two is how ASP.NET Core leaks into your domain. Here is where JWT validation, the ICurrentUserService abstraction, permission checks, and ownership rules each live, and how to keep them enforced beyond HTTP endpoints.
Where do background jobs fit in Clean Architecture? Treat them as entry points, exactly like controllers. The job schedules and triggers, the application layer does the work. Here is the structure, the scoped-service wiring that trips everyone up, and complete examples with BackgroundService and Quartz.
Setting up a Clean Architecture project from scratch takes time. Here is a complete .NET solution template with the right project structure, references, and configurations.
Clean Architecture, Onion Architecture, and Hexagonal Architecture all solve the same fundamental problem: decoupling business logic from infrastructure. But they differ in structure, naming, and emphasis. Here is a practical comparison to help you choose.
Minimal API endpoints and Clean Architecture are a natural fit: the endpoint receives HTTP, dispatches a command, and maps the result back. No business logic, no controllers, no ceremony. Here is how to organize endpoints by feature, validate with endpoint filters, and return consistent Problem Details.
Source code dependencies must only point inward. That one sentence decides your project references, where your interfaces live, and why the DI container sits at the edge. Here is how the Dependency Rule works, how control can flow outward while dependencies point inward, and how to enforce it with the compiler and architecture tests.
A domain event is handled in-process, inside the same transaction. An integration event crosses service boundaries through a message broker and needs the outbox pattern to survive a crash. Confusing the two leads to lost events and accidental coupling. Here is where the line sits, with implementation patterns for both sides.
Expected failures are not exceptional, so stop throwing them. A clear strategy gives each layer one job: the domain throws on invariant violations, the application returns typed Results, and a single global handler maps everything else to Problem Details. Here is how the pieces fit together in a .NET Clean Architecture.
A folder structure that works at 5 use cases falls apart at 200. Group by feature, keep one use case per folder, and name with verb-noun domain language, and your Application layer reads like a list of things the system can do. Here are the three layouts you will encounter, and why only one of them scales.
One pipeline behavior can log every use case with timing and failures, so handlers stay clean and the domain never sees an ILogger. Here is a layer-by-layer logging strategy for Clean Architecture: domain events instead of logs in the domain, behaviors in the application, direct logging in infrastructure, and middleware at the edge.
Request to command, command to entity, entity to response: a single operation can pass through three mappings, and most of them are ceremony. EF Core projections eliminate the read-side mapping entirely, and simple extension methods handle the rest. Here is when mapping between layers earns its keep, and why you rarely need AutoMapper.
MediatR pipeline behaviors let you handle logging, validation, caching, and transactions without touching your handlers. Here are four practical IPipelineBehavior implementations you can use today.
The Application layer is where your use cases live: commands, queries, and the handlers that orchestrate your domain objects. A good handler reads like a table of contents (load, act, save). Here is how to structure the layer in .NET and keep business logic from leaking into it.
Every business rule in your system should have exactly one home: the Domain layer. That means entities that guard their invariants, value objects instead of raw primitives, and domain events for side effects. Here is what belongs in the Domain layer, what does not, and how to keep it at zero external dependencies.
Databases, message brokers, email providers, caches: everything that talks to the outside world lives in the Infrastructure layer. It implements the interfaces your inner layers define, and it is the only place EF Core should ever appear. Here is how to structure it in .NET, from repositories and the DbContext to a clean DI registration module.
A five-endpoint CRUD API with four projects, domain events, and a CQRS pipeline is over-engineering, not discipline. Clean Architecture pays off on complex domains, long-lived codebases, and multi-team projects. Here is a decision framework for spotting which one you have, and a migration path for when you guess wrong.
The use case defines what must succeed or fail together, so the transaction boundary belongs to the application layer. Here are three ways to implement that in .NET, ranked: a single SaveChanges as the implicit boundary, a unit of work abstraction, and a transaction pipeline behavior.
Caching is infrastructure, but the decision to cache is application-level. Get that split wrong and cache concerns leak into your use cases, or worse, into your domain. Here are two clean approaches: a decorator over your repositories and a pipeline behavior driven by the use case itself.
A practical map of Clean Architecture in .NET: dependency direction, layer responsibilities, use-case organization, cross-cutting concerns, and the tradeoffs that tell you when the structure is worth its cost.
Clean Architecture is powerful when applied correctly, but easy to get wrong. Here are the most common anti-patterns and mistakes I see in .NET projects - and how to fix them.
Draw module boundaries wrong and you fight your own architecture on every feature. Bounded contexts give you a systematic way to draw them: map business capabilities, give each module its own language and data, and validate with the change test. Plus the three boundary mistakes that sink most modular monoliths.
The Ordering module publishes an event, and Shipping and Notifications react without the publisher knowing they exist. The catch: an in-process event bus runs every handler synchronously in the caller's scope, so a slow or failing consumer becomes the publisher's problem. Here is the full setup, and the point where the outbox pattern has to take over.
Build a working Modular Monolith skeleton in .NET: three modules, each with its own schema and DbContext, cross-module calls that go through Contracts projects only, and an in-process event bus. Architecture tests fail the build the moment someone crosses a boundary.
Placing an order touches Ordering, Inventory, Payment, and Shipping, and any step can fail after the previous ones already committed. The saga pattern breaks the process into local transactions with compensating actions, coordinated by an orchestrator that fits in one class. Here is how to build one inside a modular monolith, without a distributed transaction in sight.
Schema-per-module and database-per-module both enforce module boundaries at the data layer, but they differ sharply on transactions, operations, and your path to microservices. Here is the EF Core setup for both, the five trade-offs that actually matter, and the triggers that tell you when to graduate from schemas to separate databases.
Modules need to share some code without becoming coupled. The shared kernel pattern defines a small, explicit set of shared types that all modules can depend on.
Big-bang rewrites fail for predictable reasons: they take longer than planned, the legacy system keeps changing underneath them, and the business sees nothing until the end. The Strangler Fig pattern replaces a legacy monolith one bounded context at a time, with feature-flag routing, data sync, and a parallel run before every cutover. The system stays fully functional throughout.
Extract a module too early and you lock unstable boundaries into network contracts. Wait too long and extraction becomes a multi-month rewrite. These are the four concrete drivers that justify extracting a module into a microservice, the readiness checklist to pass first, and the extraction process that keeps a rollback path open.
The dual-write problem silently corrupts distributed systems. The Outbox pattern eliminates it by turning two unreliable writes into one atomic operation.
A modular monolith keeps one deployment while enforcing business boundaries inside the codebase. This guide connects module design, data isolation, communication, testing, and the evidence that can justify extracting a service later.
Modular Monoliths give you most of microservices benefits - loose coupling, independent modules, clear boundaries - without the operational complexity. Here is a practical comparison to help you decide which fits your project.
CQRS and Vertical Slice Architecture are a natural pair. Commands and queries are already separate - putting each in its own slice makes them independent and easy to optimize.
One criticism of Vertical Slice Architecture is code duplication across slices. Here is how to handle cross-cutting concerns like validation, logging, and caching without breaking feature isolation.
Stop organizing code by technical concern (Controllers, Services, Models). Organize by feature instead - so everything related to placing an order lives in one folder. Here is how to implement feature folders in .NET.
Vertical slices change the shape of your tests. Instead of repository, service, and controller tests held together by mocks, you test one feature at a time: validators in microseconds, handlers in isolation, and the full slice from HTTP to database with WebApplicationFactory and Testcontainers.
Clean Architecture manages complexity through layer discipline. Vertical Slice Architecture manages it through feature isolation. Teams treat this as a religious war, but it is a fit question: rich shared domain logic points one way, features with wildly different complexity point the other. Here is how the two compare, and the hybrid most real projects land on.
Carter turns Minimal API endpoints into self-registering modules: each feature defines its routes, and app.MapCarter() wires them up at startup. Combined with Vertical Slice Architecture, every feature owns its endpoint, handler, and validator in one folder. Here is the full setup, from installation to integration tests.
Four files across four folders for a query that returns one object: that is the layered tax on every feature. Sometimes the tax buys you real structure, and sometimes it buys you nothing. Here are the concrete signals that tell you when Vertical Slice Architecture is the better fit for your .NET project, and when layers still win.
Vertical Slice Architecture organizes code around behavior. Learn slice boundaries, validation, testing, and how VSA works with CQRS and Clean Architecture.
The REPR pattern (Request-Endpoint-Response) replaces bloated controllers with focused, single-purpose endpoints. Here is how to implement it in ASP.NET Core with Minimal APIs.
Aggregates are the most important tactical pattern in Domain-Driven Design. They define consistency boundaries, enforce invariants, and protect your domain model. Here are the rules and practical guidance for designing aggregates in .NET.
The Aggregate Root is the gatekeeper of consistency in Domain-Driven Design. Here are the rules for designing aggregate roots and implementing them in C#.
An Anti-Corruption Layer prevents external systems from polluting your domain model. It translates between your clean domain language and the messy reality of legacy systems or third-party APIs.
Application services coordinate use cases. Domain services hold business rules that do not fit one entity or value object. These .NET examples show where loading, transactions, and decisions belong, including the edge cases around repositories and concurrency.
There are five practical ways to implement business rules in C#: guard clauses, rule objects, the result pattern, value objects, and factory methods. Here is how each one works and when to reach for it.
Conformist, partnership, customer-supplier: context mapping patterns are not diagram decorations. They are power dynamics that predict which team absorbs breaking changes. Here is each pattern, when it happens to you, and what it looks like in .NET code.
Domain Services handle business logic that does not naturally belong to a single entity or value object. Here is when to use them, how to implement them in .NET, and how they differ from Application Services.
Domain-Driven Design connects business language and boundaries to the rules in your code. Start with bounded contexts, then implement entities, value objects, and aggregate transitions in plain C#.
Exposing mutable collections on domain entities breaks encapsulation and lets callers bypass business rules. Here is how to encapsulate collections properly in C#.
Entities have identity. Value Objects have equality by attributes. Knowing when to use each is fundamental to building a strong domain model. Here is a practical comparison with C# examples.
Build an event-sourced aggregate, an append-only event store, and a replayable read model in C#. This learning implementation makes version checks and duplicate delivery explicit, then explains what durable storage and snapshots add.
EventStorming brings domain experts and developers together to explore a business process through events. Use the timeline to expose missing rules, investigate possible bounded contexts, and choose which behaviors to model in .NET.
Factories encapsulate complex object creation logic in DDD. Here is when and how to use factories for creating aggregates, entities, and value objects in C#.
Value objects have no identity - they are defined by their properties. EF Core gives you three ways to persist them: owned types, complex types, and value conversions. Here is when to use each.
EF Core can persist rich domain models without public setters or mapping attributes. Private constructors, backing fields, and external configuration keep database concerns out of business methods. Here are the mappings and the compromises they still require.
A saga coordinates local transactions and recovery across services. A process manager tracks workflow state and decides the next step. They often work together: the process manager orchestrates the saga and its compensations.
An anemic domain model separates data from behavior. A rich domain model keeps them together. Here is why it matters, how to refactor from anemic to rich, and when each approach is appropriate.
The Specification pattern encapsulates query logic into reusable objects. Combined with EF Core, it eliminates query duplication and keeps your repositories clean. Here is how to implement it in C#.
Strategic DDD tells you where to draw boundaries. Tactical DDD tells you how to model within those boundaries. Most teams skip strategy and jump straight to tactics - and that is a mistake.
Passing Guid parameters around is error-prone. Strongly typed IDs wrap primitives in domain-specific types so you cannot accidentally pass an OrderId where a CustomerId is expected. Here is how to implement them in C# with EF Core support.
An always-valid domain model enforces business invariants at construction and on every state change. Private constructors, validated value objects, and guarded methods reduce repeated validation while keeping persistence and concurrency checks explicit.
A transaction script puts a use case in one procedure; a domain model puts business rules inside domain objects. Compare equivalent C# order-pricing examples, the testing tradeoffs, and a gradual path from scripts to a richer model.
Ubiquitous language puts the same business terms in conversations, code, and tests. Use precise operation names, respect differences between bounded contexts, and refine the vocabulary as the model evolves.
Bounded contexts let Ordering and Fulfillment use different models of the same concepts. Define those boundaries in .NET, translate integration contracts, and keep ownership of models and data explicit.
You probably do not need Redis, RedLock, or ZooKeeper for distributed locking. If your instances already share a Postgres database, they share a lock manager too. Session-level advisory locks serialize work across every instance and auto-release the moment a holder dies, which is the exact property TTL-based leases spend fencing tokens trying to approximate.
Your query is correct, your tests pass, and the index you carefully created is never touched. The usual culprit is one innocent-looking habit: wrapping the column in a function. Here is what sargability means, how to catch the problem with EXPLAIN, and how I built a coding kata that grades your query plan so the lesson actually sticks.
Dapper's value proposition is predictability: the SQL you write is the SQL that runs, with object mapping and nothing else. This guide covers the patterns you need in a real application: queries, parameters, multi-mapping, QueryMultiple, transactions, and where Dapper fits next to EF Core.
Cache invalidation is hard because we name cache entries after locations. Name them after a hash of their inputs instead, and the whole class of stale-read bugs stops being possible: change any input, get a different key, recompute under it. Git, Docker, and NuGet all run on this idea, and it works just as well inside your own application.
Rolling your own IMemoryCache plus Redis gets you 80% of a caching stack and silently skips the three hard parts: cross-instance invalidation, cache stampede, and serving stale data when the source is down. FusionCache gives you L1, L2, a backplane, and all three behaviors out of the box.
My first job queue was a Redis list, and it had two ways to silently lose a job: a worker crash after LPOP, and a broker restart. NATS JetStream closed both with a work-queue stream, a durable pull consumer, and one ordering rule: publish the result before you ack the job. Here is the whole design, small enough to read in five minutes.
RabbitMQ deletes messages once consumed; Kafka keeps them and moves a cursor. That single design decision explains almost every difference between them: replay, fan-out, ordering, routing, and scaling. Understand it and the choice for your .NET system mostly makes itself.
One OpenTelemetry Collector is easy. The interesting design shows up the moment your apps run on more than one machine: a small agent collector on every box, forwarding to one gateway collector that fans out to Tempo, Loki, and Prometheus. Here is the two-tier setup I run in production, including the two config lines that stop the collector from OOM-killing the box it shares with real workloads.
OpenTelemetry is the standard for distributed tracing, metrics, and logging. Here is how to instrument your .NET application and export telemetry to Jaeger, Prometheus, and Seq.
An architecture rule nobody enforces is a wish with a code-review lottery attached. A fitness function turns it into a build failure that names the offending type. Here is how to write them with ArchUnitNET, including slice cycle detection, the three ways a fitness function quietly lies to you, and what to do when your rule set outgrows code.
Unit tests should be fast, reliable, and maintainable. But poorly written tests become a burden that slows development. Here are the best practices for writing effective unit tests in .NET.
Every RAG demo works until you point it at your own documents and it confidently makes things up. The model is rarely the problem. Chunking, embeddings, and retrieval quality decide whether the answer comes from your data or from thin air. Here is a complete RAG pipeline in .NET with Microsoft.Extensions.AI, PostgreSQL with pgvector, hybrid search, re-ranking, and source attribution.
Nobody argues about 200 and 500. The fights are in the middle: 400 vs 422, 401 vs 403, 404 vs 410, and whether 200-with-an-error-body is ever acceptable (it is not). A decision tree for the ambiguous cases, with the ASP.NET Core mappings to make it stick.
Minimal APIs are a lightweight way to build HTTP APIs in .NET without controllers, startup classes, or conventions. Here is a complete guide covering routing, validation, authentication, and structuring Minimal APIs at scale.
Records in C# provide value equality, immutability, and concise syntax. But should you use them everywhere? Here is when records shine and when they do not.
Docker simplifies deployment by packaging your .NET application with its dependencies into a container. Here is everything you need to know - from writing a Dockerfile to Docker Compose for local development.
The Saga pattern manages distributed transactions across multiple services by breaking them into local transactions with compensating actions. Here is how to implement both orchestration and choreography sagas in .NET.
A .NET microservice system needs more than separately deployed APIs: each service owns its data, communication survives partial failure, and traces cross every boundary. This guide builds a small end-to-end system and makes the cost visible before you choose it.
Async improves throughput only while the work stays nonblocking. Sync-over-async, accidental serialization, unnecessary state machines, and unbounded fan-out can make an asynchronous endpoint slower or deadlock it entirely.
Native EventSource cannot send an Authorization header or a POST body. Use @microsoft/fetch-event-source to consume SSE with bearer tokens, validate responses, control retries, and clean up connections in React. The server still owns event routing and replay.
Polly v8 replaced policies with resilience pipelines: a rewritten core with allocation-free execution, built-in telemetry, and one composition model instead of PolicyWrap. Here is how the new API maps to the old one, how to register pipelines with DI, and why the order you add strategies changes what your pipeline actually does.
JWT tokens are the standard way to authenticate APIs. Here is a complete guide to setting up JWT authentication in ASP.NET Core - from token generation to validation, refresh tokens, and common security mistakes.