Author: foyezahmedfj20@gmail.com

  • Unit Test vs Integration Test vs End-to-End Test: Which One Should You Write?

    When I first started learning software testing, I thought:

    “Why do we need so many different types of tests?”

    If an End-to-End (E2E) test verifies the whole application, isn’t that enough?

    Over time, I realized that each type of test answers a different question.

    Understanding that changed how I think about testing.


    1. Unit Test

    A unit test verifies a single unit of code, usually a method or class, in isolation.

    Dependencies such as databases, APIs, or file systems are replaced with mocks or fakes.

    Example:

    • Does CalculateDiscount() return the correct value?
    • Does OrderService apply business rules correctly?

    Characteristics

    • Very fast
    • Easy to debug
    • Highly reliable
    • Runs frequently during development

    2. Integration Test

    An integration test verifies that multiple components work correctly together.

    Instead of mocking everything, it tests real interactions.

    Example:

    • Can the API save data to the database?
    • Does Entity Framework Core correctly persist entities?
    • Does the application communicate properly with Redis or RabbitMQ?

    Characteristics

    • Slower than unit tests
    • Tests real infrastructure
    • Catches configuration and integration issues

    3. End-to-End (E2E) Test

    An End-to-End test validates the entire application from the user’s perspective.

    Example:

    1. User logs in.
    2. Creates an order.
    3. Makes a payment.
    4. Receives a confirmation.

    Everything—from the UI to the database—is tested together.

    Characteristics

    • Slowest type of test
    • Closest to real user behavior
    • Excellent for validating critical business workflows

    Quick Comparison

    FeatureUnit TestIntegration TestEnd-to-End Test
    ScopeSingle method/classMultiple componentsEntire application
    DependenciesMockedRealReal
    SpeedFastMediumSlow
    PurposeVerify business logicVerify component interactionVerify complete user journey

    Which One Should You Write?

    The answer isn’t one or the other.

    A healthy test suite usually contains all three.

    • Write Unit Tests for business logic.
    • Write Integration Tests for APIs, databases, and external services.
    • Write End-to-End Tests for critical user journeys.

    Each type catches different kinds of problems.


    Final Thoughts

    One lesson I’ve learned is this:

    The goal of testing isn’t to increase the number of tests. It’s to increase confidence in your software.

    The best strategy is to balance speed, reliability, and coverage by choosing the right test for the right scenario.

  • ASP.NET Core Filters: Stop Repeating the Same Code in Every Controller

    ASP.NET Core Filters: Stop Repeating the Same Code in Every Controller

    When I first started building ASP.NET Core APIs, I noticed a pattern.

    Many controller actions contained the same code:

    • Validate requests.
    • Check permissions.
    • Log execution time.
    • Handle exceptions.
    • Write audit logs.

    The APIs worked.

    But every new endpoint meant copying the same logic again.

    That’s when I discovered Filters.

    I realized they weren’t just another ASP.NET Core feature—they were a way to keep controllers focused on business logic while moving cross-cutting concerns to reusable components.

    What Are Filters?

    Filters allow you to execute code before or after an action method runs.

    Instead of repeating common logic in every controller, you write it once and apply it wherever it’s needed.


    Types of Filters

    Authorization Filter

    Runs first and determines whether the request is authorized.

    Example:

    • Check user permissions.
    • Validate custom authorization rules.

    Resource Filter

    Executes before model binding.

    Useful for:

    • Caching
    • Short-circuiting requests
    • Resource initialization

    Action Filter

    Runs before and after the controller action.

    Common use cases:

    • Input validation
    • Logging
    • Measuring execution time
    • Auditing

    Exception Filter

    Handles exceptions thrown by controller actions.

    Useful for:

    • Logging exceptions
    • Returning consistent error responses

    Result Filter

    Runs before and after the action result is executed.

    Useful for:

    • Modifying response headers
    • Wrapping API responses
    • Adding metadata

    When Should You Use Filters?

    Filters are ideal for logic that applies across multiple endpoints.

    Examples include:

    • Audit logging
    • Request validation
    • Performance monitoring
    • Response formatting
    • Custom authorization

    If you find yourself copying the same code into multiple controllers, it’s often a sign that a filter could help.


    When Should You Avoid Filters?

    Filters aren’t the answer for every problem.

    Choose the right tool for the job:

    • Middleware for application-wide concerns such as authentication, CORS, or request logging.
    • Filters for MVC or API action-specific behavior.
    • Services for business logic.

    Keeping these responsibilities separate leads to a cleaner architecture.


    Final Thoughts

    One lesson changed the way I structure APIs:

    Controllers should coordinate requests—not perform every supporting task themselves.

    Filters help eliminate duplication, improve maintainability, and keep your business logic where it belongs.

  • Background Job Processing: Just Because You Can Do It in the Request Doesn’t Mean You Should

    Background Job Processing: Just Because You Can Do It in the Request Doesn’t Mean You Should

    Early in my career, I had a simple approach.

    A user submitted a request, and the API did everything before returning a response.

    • Save the data.
    • Send an email.
    • Generate a PDF.
    • Upload files.
    • Notify other systems.

    The endpoint worked.

    But it was also slow.

    Then I realized something important:

    Not every task belongs in the request-response cycle.

    Some tasks don’t need to finish before the user receives a response.

    That’s where background job processing becomes valuable.

    What Is Background Job Processing?

    A background job allows your application to offload long-running or non-critical work to execute after the request has completed.

    Instead of making the user wait, the API responds quickly while the background worker processes the remaining tasks.


    What Should Run in the Background?

    Typical examples include:

    • Sending emails or SMS messages.
    • Generating PDF or Excel reports.
    • Processing uploaded files.
    • Creating thumbnails or resizing images.
    • Synchronizing data with external systems.
    • Publishing events or notifications.

    These operations don’t usually need to block the user’s request.


    Benefits

    • Faster API response times.
    • Better user experience.
    • Improved scalability under heavy load.
    • Better resilience when external services are slow.

    Things to Consider

    Background jobs introduce new responsibilities.

    Think about:

    • Retry policies.
    • Error handling.
    • Monitoring and logging.
    • Idempotency to avoid duplicate processing.
    • Queue management.

    Moving work to the background doesn’t remove complexity—it moves it to a different part of the system.


    Common Tools in .NET

    Depending on your requirements, you might choose:

    • Hangfire
    • Quartz.NET
    • Azure Functions
    • Worker Services
    • Message queues such as RabbitMQ or Azure Service Bus

    The best tool depends on your application’s architecture and operational needs.


    Final Thoughts

    One lesson changed how I design APIs:

    Respond as soon as you’ve completed the work the user actually needs.

    Everything else should be evaluated to see whether it belongs in a background job.

    Fast APIs aren’t always the ones that do less work.

    They’re often the ones that do the right work at the right time.

  • High Concurrency Handling: What Happens When Thousands of Users Hit Your API at the Same Time?

    High Concurrency Handling: What Happens When Thousands of Users Hit Your API at the Same Time?

    Imagine your application is running smoothly.

    Then your company launches a flash sale.

    Or a marketing campaign goes live.

    Suddenly, thousands of users start sending requests to the same endpoint at the same time.

    The question is no longer:

    “Does my API work?”

    It’s:

    “Can my API still work under heavy load?”

    Handling high concurrency isn’t about a single optimization. It’s about building a system that remains reliable as traffic grows.

    Here are some techniques I rely on.


    1. Avoid Blocking Threads

    In ASP.NET Core, every blocked thread reduces your application’s ability to handle incoming requests.

    Prefer asynchronous APIs whenever possible.

    await repository.GetOrdersAsync();
    

    Using async and await allows the thread to serve other requests while waiting for I/O operations.


    2. Optimize Database Access

    The database is often the first bottleneck.

    Reduce unnecessary work by:

    • Selecting only the required columns.
    • Adding proper indexes.
    • Using AsNoTracking() for read-only queries.
    • Avoiding the N+1 query problem.

    A faster query means every request spends less time waiting.


    3. Cache Frequently Requested Data

    Not every request needs to reach the database.

    Cache data that changes infrequently using solutions such as Redis or the in-memory cache.

    Reducing database traffic significantly improves throughput.


    4. Limit Expensive Operations

    Some endpoints perform CPU-intensive or long-running work.

    Protect your application with:

    • Rate limiting.
    • Background processing.
    • Queues for non-critical tasks.

    This prevents a small number of requests from consuming all available resources.


    5. Use Optimistic Concurrency

    When multiple users update the same record simultaneously, conflicts can occur.

    EF Core supports optimistic concurrency using concurrency tokens such as RowVersion.

    This helps prevent accidental overwrites.


    6. Scale Horizontally

    Sometimes optimization isn’t enough.

    Run multiple application instances behind a load balancer so incoming requests are distributed across servers.

    This increases capacity while improving availability.


    7. Monitor Before You Optimize

    Use tools such as:

    • Grafana
    • Prometheus
    • Application Insights
    • OpenTelemetry

    Measure:

    • Response time
    • Error rate
    • CPU usage
    • Database latency
    • Throughput

    You can’t improve what you don’t measure.


    Final Thoughts

    High concurrency isn’t just about surviving traffic spikes.

    It’s about designing systems that continue to respond quickly, remain reliable, and protect shared resources under pressure.

    Performance isn’t achieved through one big optimization.

    It’s the result of many small engineering decisions working together.

  • Caching Strategies Every Backend Developer Should Know

    Caching Strategies Every Backend Developer Should Know

    A few years ago, I thought caching was simple.

    Need better performance?

    Just put it in the cache.

    Problem solved.

    But after working on production systems, I realized that caching isn’t just about making applications faster.

    It’s about choosing the right caching strategy.

    The wrong strategy can lead to stale data, unnecessary database calls, or even cache stampedes.

    Here are the four most common caching strategies every backend developer should know.


    1. Cache-Aside (Lazy Loading)

    This is the most commonly used strategy.

    How it works:

    1. Check the cache.
    2. If the data exists, return it.
    3. If not, retrieve it from the database.
    4. Store it in the cache for future requests.

    Best for:

    • Product catalogs
    • User profiles
    • Frequently read data

    Pros

    ✅ Simple to implement.

    ✅ Cache stores only requested data.

    Cons

    • The first request is always slower (cache miss).
    • Data can become stale if not invalidated properly.

    2. Read-Through

    Instead of the application talking directly to the database, the cache is responsible for loading missing data.

    The application only communicates with the cache.

    Best for:

    • Systems with high read traffic.
    • Centralized caching layers.

    Pros

    ✅ Simpler application code.

    ✅ Consistent cache behavior.

    Cons

    • Depends on cache provider support.

    3. Write-Through

    Whenever data is written, it’s stored in both the cache and the database.

    This keeps the cache synchronized.

    Best for:

    • Applications where data consistency is important.

    Pros

    ✅ Cache always contains the latest data.

    Cons

    • Write operations become slightly slower.

    4. Write-Behind (Write-Back)

    The application writes to the cache first.

    The cache updates the database asynchronously.

    Best for:

    • High-write workloads.
    • Logging.
    • Analytics.
    • Telemetry.

    Pros

    ✅ Very fast write performance.

    Cons

    • Risk of data loss if the cache fails before persisting changes.

    Which Strategy Should You Choose?

    ScenarioRecommended Strategy
    Read-heavy applications✅ Cache-Aside
    Centralized cache layer✅ Read-Through
    Strong consistency✅ Write-Through
    High write throughput✅ Write-Behind

    Final Thoughts

    Caching isn’t just about speed.

    It’s about balancing:

    • Performance
    • Consistency
    • Complexity
    • Scalability

    My biggest realization was this:

    The fastest application isn’t the one that caches everything. It’s the one that caches the right data using the right strategy.

    Choose your caching strategy based on your application’s requirements—not because it’s the most popular pattern.

  • CORS in ASP.NET Core: It’s More Than Just Fixing a Browser Error

    CORS in ASP.NET Core: It’s More Than Just Fixing a Browser Error

    I’ll admit something.

    For a long time, whenever I saw this error:

    “Access to fetch at ‘https://api.example.com‘ from origin ‘https://localhost:3000‘ has been blocked by CORS policy.”

    My first reaction was simple:

    “Let me add AllowAnyOrigin() and move on.”

    The application worked.

    The error disappeared.

    Problem solved… or so I thought.

    Later, I decided to understand what CORS was actually doing.

    And I realized something important:

    CORS isn’t a server feature. It’s a browser security feature.

    Your API might happily return a response.

    But the browser blocks JavaScript from accessing it if the request violates the server’s CORS policy.

    That’s why Postman works while the browser fails.


    What Is CORS?

    CORS (Cross-Origin Resource Sharing) is a mechanism that allows a server to specify which origins can access its resources.

    An origin consists of:

    • Protocol (https)
    • Domain (example.com)
    • Port (5001)

    If any of these differ, the browser considers it a different origin.

    For example:

    https://localhost:3000
    

    and

    https://localhost:5001
    

    are different origins.


    Configuring CORS in ASP.NET Core

    Register a policy:

    builder.Services.AddCors(options =>
    {
        options.AddPolicy("FrontendPolicy", policy =>
        {
            policy.WithOrigins("https://localhost:3000")
                  .AllowAnyHeader()
                  .AllowAnyMethod();
        });
    });
    

    Apply the policy:

    app.UseCors("FrontendPolicy");
    

    Avoid This in Production

    .AllowAnyOrigin()
    

    While it’s convenient during development, allowing every origin in production can expose your API unnecessarily.

    Instead, explicitly specify the trusted origins your application should allow.


    Middleware Order Matters

    A common mistake is placing UseCors() in the wrong position.

    A typical pipeline looks like this:

    app.UseRouting();
    
    app.UseCors("FrontendPolicy");
    
    app.UseAuthentication();
    
    app.UseAuthorization();
    
    app.MapControllers();
    

    If CORS isn’t applied at the correct point in the pipeline, the browser may still block requests.


    Final Thoughts

    Today, I don’t configure CORS just to remove an error message.

    I configure it knowing why the browser enforces it and how it protects users.

    Understanding the reason behind a feature is far more valuable than memorizing the code to make an error disappear.

  • Change Tracking in EF Core: What It Is and When Should You Use It?

    Change Tracking in EF Core: What It Is and When Should You Use It?

    One of the most powerful features of Entity Framework Core is Change Tracking.

    It allows EF Core to automatically detect changes made to entities and persist them to the database.

    But while it’s incredibly useful, it’s not always necessary.

    Understanding when to use Change Tracking—and when to disable it—can significantly improve your application’s performance.


    What Is Change Tracking?

    When EF Core retrieves an entity, it keeps track of its original state.

    If you modify one of its properties, EF Core detects the change.

    When you call SaveChanges(), only the modified values are sent to the database.

    Example:

    var product = await context.Products
        .FirstAsync(p => p.Id == 1);
    
    product.Price = 1200;
    
    await context.SaveChangesAsync();
    

    Notice that we never call Update().

    EF Core already knows that Price changed.


    How Does It Work?

    When an entity is loaded:

    • EF Core stores the original values.
    • It monitors property changes.
    • During SaveChanges(), it compares the current values with the original values.
    • Only the necessary SQL UPDATE statement is generated.

    This makes updates simple and reduces boilerplate code.


    When Should You Use Change Tracking?

    Change Tracking is ideal when:

    • Updating existing records.
    • Inserting related entities.
    • Managing relationships.
    • Performing CRUD operations where data will be modified.

    When Should You Avoid It?

    If you’re only reading data, Change Tracking adds unnecessary overhead.

    In those cases, use:

    var products = await context.Products
        .AsNoTracking()
        .ToListAsync();
    

    This avoids tracking every entity and can improve performance, especially for large result sets.


    Change Tracking vs AsNoTracking()

    With Change Tracking

    var product = await context.Products
        .FirstAsync();
    
    product.Price = 1200;
    
    await context.SaveChangesAsync();
    

    ✅ Changes are automatically detected and saved.


    With AsNoTracking()

    var product = await context.Products
        .AsNoTracking()
        .FirstAsync();
    

    The entity is not tracked.

    If you modify it and call SaveChanges(), nothing happens unless you explicitly attach or update the entity.


    Performance Considerations

    Tracking every entity consumes:

    • Memory
    • CPU
    • Change detection time

    For read-heavy APIs or reporting queries, disabling tracking can provide noticeable performance improvements.

    However, don’t disable tracking if you intend to modify the entities afterwards.


    Best Practices

    ✅ Use Change Tracking for create, update, and delete operations.

    ✅ Use AsNoTracking() for read-only queries.

    ✅ Avoid tracking thousands of entities unnecessarily.

    ✅ Understand whether your query needs tracked or untracked entities before optimizing.


    Final Thoughts

    Change Tracking is one of the reasons EF Core makes data updates so convenient.

    But like any feature, it should be used intentionally.

    A simple rule I follow is:

    • Reading data? → Use AsNoTracking().
    • Updating data? → Let EF Core track the entity.

    Choosing the right approach helps you build applications that are both clean and efficient.

  • Monolith vs Microservices: Which Architecture Should You Choose?

    Monolith vs Microservices: Which Architecture Should You Choose?

    One of the most common architecture debates is:

    Should I build a Monolith or Microservices?

    Many developers assume microservices are always the better choice because large companies like Netflix, Amazon, and Uber use them.

    The reality?

    Most applications don’t need microservices on day one.

    Let’s compare both approaches.


    What Is a Monolithic Architecture?

    In a monolithic application, all modules—such as authentication, products, orders, and payments—are part of a single application and are deployed together.

    Advantages

    ✅ Simple to develop and deploy.

    ✅ Easier to debug.

    ✅ Lower operational complexity.

    ✅ Faster to get started.

    Challenges

    • Scaling means scaling the entire application.
    • A bug in one module can affect the whole system.
    • Large codebases become harder to maintain over time.

    What Are Microservices?

    In a microservices architecture, the application is divided into independent services.

    For example:

    • Authentication Service
    • Order Service
    • Product Service
    • Payment Service

    Each service can be developed, deployed, and scaled independently.

    Advantages

    ✅ Independent deployments.

    ✅ Scale only the services that need it.

    ✅ Better fault isolation.

    ✅ Teams can work independently.

    Challenges

    • More complex infrastructure.
    • Service-to-service communication.
    • Distributed logging and monitoring.
    • Data consistency across services.
    • Higher operational cost.

    When Should You Choose a Monolith?

    A monolith is often the better choice when:

    • You’re building a new product or MVP.
    • The team is small.
    • Requirements are still evolving.
    • Deployment simplicity is important.

    A well-designed modular monolith can support significant growth before microservices become necessary.


    When Should You Choose Microservices?

    Microservices become more valuable when:

    • Different parts of the system have different scaling needs.
    • Multiple teams work independently.
    • Independent deployments are required.
    • The application has grown too large for a single codebase.

    Comparison

    FeatureMonolithMicroservices
    DeploymentSingle deploymentIndependent deployments
    ScalabilityEntire applicationIndividual services
    ComplexityLowerHigher
    DevelopmentEasier initiallyMore planning required
    Team CollaborationBetter for small teamsBetter for multiple teams
    InfrastructureSimpleMore complex

    My Rule of Thumb

    • Small to medium applications: Start with a modular monolith.
    • Large, rapidly growing systems with multiple teams: Consider microservices when there is a clear business or technical need.

    Choosing microservices too early can introduce unnecessary complexity.


    Final Thoughts

    Architecture isn’t about following trends.

    It’s about solving the right problem with the right level of complexity.

    A well-structured monolith is often a better choice than poorly designed microservices.

    Start simple, measure your application’s needs, and evolve your architecture when the benefits outweigh the added complexity.

  • 7 EF Core Query Optimization Tips Every .NET Developer Should Know

    7 EF Core Query Optimization Tips Every .NET Developer Should Know

    Entity Framework Core makes database access simple, but it’s also easy to write queries that look correct while performing poorly in production.

    As applications grow, inefficient queries can become one of the biggest performance bottlenecks.

    Here are seven practical techniques I regularly consider when working with EF Core.


    1. Select Only the Columns You Need

    Avoid loading an entire entity if you only need a few properties.

    ❌ Less efficient:

    var users = await context.Users.ToListAsync();
    

    ✅ Better:

    var users = await context.Users
        .Select(u => new
        {
            u.Id,
            u.Name
        })
        .ToListAsync();
    

    Fetching fewer columns reduces network traffic and memory usage.


    2. Use AsNoTracking() for Read-Only Queries

    By default, EF Core tracks every entity it loads.

    If you’re only reading data, disable change tracking.

    var products = await context.Products
        .AsNoTracking()
        .ToListAsync();
    

    This can noticeably improve performance for read-heavy operations.


    3. Avoid the N+1 Query Problem

    This is a common performance issue.

    Instead of triggering additional queries for related data, load it efficiently.

    var orders = await context.Orders
        .Include(o => o.Customer)
        .ToListAsync();
    

    Or project only the data you actually need.


    4. Filter Early

    Push filtering to the database.

    var activeUsers = await context.Users
        .Where(u => u.IsActive)
        .ToListAsync();
    

    Don’t retrieve unnecessary rows and filter them in memory.


    5. Paginate Large Result Sets

    Avoid returning thousands of records at once.

    var customers = await context.Customers
        .OrderBy(c => c.Id)
        .Skip(page * pageSize)
        .Take(pageSize)
        .ToListAsync();
    

    Pagination improves both application performance and user experience.


    6. Check the Generated SQL

    EF Core generates SQL for you—but you should still know what it’s executing.

    Use:

    var sql = query.ToQueryString();
    

    Reviewing the generated SQL helps identify unnecessary joins, filters, or inefficient queries before they reach production.


    7. Create the Right Database Indexes

    Even the best EF Core query can perform poorly without proper indexing.

    Frequently filtered or joined columns should be indexed appropriately.

    Remember:

    EF Core generates SQL. SQL Server executes it.

    Database design still matters.


    Final Thoughts

    Optimizing EF Core isn’t about replacing it with raw SQL.

    It’s about understanding how your LINQ queries translate into SQL and ensuring the database performs as little work as necessary.

    A few simple habits can make a significant difference:

    • Project only what you need.
    • Use AsNoTracking() for read-only queries.
    • Avoid N+1 queries.
    • Filter early.
    • Paginate large datasets.
    • Inspect generated SQL.
    • Design proper indexes.

    Small optimizations applied consistently often produce the biggest improvements.

  • Lazy Loading vs Eager Loading vs Explicit Loading in EF Core: Which One Should You Use?

    Lazy Loading vs Eager Loading vs Explicit Loading in EF Core: Which One Should You Use?

    When working with Entity Framework Core, loading related data efficiently is just as important as writing correct queries.

    EF Core provides three approaches:

    • Eager Loading
    • Lazy Loading
    • Explicit Loading

    Each has its own strengths and trade-offs.

    Choosing the wrong one can lead to unnecessary database calls and poor application performance.

    Let’s explore when to use each.


    1. Eager Loading

    Eager loading retrieves the main entity and its related data in a single query using Include().

    Example:

    var orders = await context.Orders
        .Include(o => o.Customer)
        .ToListAsync();
    

    Here, both Orders and their related Customer data are loaded together.

    Use Eager Loading When:

    • You know you’ll need the related data.
    • You want to minimize database round trips.
    • You’re building APIs that return complete object graphs.

    Pros

    • Fewer database queries.
    • Better performance in many scenarios.
    • Easy to understand.

    Cons

    • Can retrieve unnecessary data if overused.
    • Multiple Include() calls may produce complex SQL queries.

    2. Lazy Loading

    With lazy loading, related data isn’t loaded until it’s actually accessed.

    Example:

    var order = await context.Orders.FirstAsync();
    
    var customer = order.Customer;
    

    The second line triggers another database query.

    Use Lazy Loading When:

    • Related data is rarely needed.
    • Simplicity is more important than maximum performance.

    Pros

    • Loads data only when needed.
    • Cleaner code.

    Cons

    • Can generate many hidden database queries.
    • May lead to the N+1 query problem.
    • Less predictable performance.

    3. Explicit Loading

    Explicit loading gives you full control over when related data is retrieved.

    Example:

    var order = await context.Orders.FirstAsync();
    
    await context.Entry(order)
        .Reference(o => o.Customer)
        .LoadAsync();
    

    The related entity is loaded only when you explicitly request it.

    Use Explicit Loading When:

    • You only need related data in specific scenarios.
    • You want predictable database access.
    • You’re optimizing performance-critical code.

    Pros

    • Full control over database queries.
    • Avoids unnecessary loading.

    Cons

    • Requires additional code.
    • Easier to forget loading related entities.

    Beware of the N+1 Query Problem

    Consider this:

    var orders = await context.Orders.ToListAsync();
    
    foreach (var order in orders)
    {
        Console.WriteLine(order.Customer.Name);
    }
    

    If lazy loading is enabled, EF Core may execute:

    • 1 query for all orders.
    • 1 additional query for each customer.

    If there are 100 orders, that could result in 101 database queries.

    This is known as the N+1 query problem, and it can significantly impact performance.


    Which One Should You Choose?

    ScenarioRecommended
    You already know you’ll need related data✅ Eager Loading
    Related data is optional and rarely used✅ Lazy Loading
    You need precise control over when data is loaded✅ Explicit Loading

    Final Thoughts

    There isn’t a single “best” loading strategy.

    The right choice depends on your application’s requirements.

    My general rule is:

    • Prefer Eager Loading for APIs and common queries.
    • Use Explicit Loading when you need fine-grained control.
    • Use Lazy Loading carefully, especially in high-performance applications, to avoid hidden database queries.

    Understanding these loading strategies can help you build faster, more efficient EF Core applications.