Skip to main content
SEMastery
Data Accessbeginner

EF Core DbContext Options Explained: A Beginner's Friendly Guide

Learn EF Core DbContext options in simple words: AddDbContext, the options builder, retry on failure, query splitting, logging, lifetimes and pooling, with diagrams and examples.

12 min readUpdated April 27, 2026

A tea stall and its recipe card

Picture a small tea stall near a railway station. The owner, Ravi, makes the same chai again and again all day. Before the rush starts in the morning, he writes a small recipe card: how much milk, how much sugar, which stove to use, what to do if the gas runs low, and whether to make one big pot or many small cups.

Once that card is ready, Ravi does not think about these choices for every customer. He just follows the card. The card was set up once, and it shapes every cup he serves.

In Entity Framework Core, your DbContext is the chai maker. The recipe card is the set of options you give it. You write these options one time when your app starts. After that, every database call follows the same card. The object you use to write the card is called the DbContextOptionsBuilder, and the finished card is a DbContextOptions object.

This article explains that recipe card in simple words. We will look at where the options live, the most useful ones, and the mistakes that trip up beginners.

What is a DbContext, really?

A DbContext is your bridge to the database. It knows your tables, tracks the changes you make to objects, and turns your C# code into SQL. But a context cannot do its job until it knows a few things first:

  • Which database are we talking to? SQL Server? PostgreSQL? SQLite?
  • Where is that database? This is the connection string.
  • How should it behave? Should it retry on errors? Should it log queries? Should it split big queries?

All of these answers go into the options. The starting point for every one of them is the DbContextOptionsBuilder.

The options builder shapes the DbContext, which then talks to the database.

Where do the options come from?

There are three common places to set options. Beginners get confused because tutorials mix them. Let us keep them clear.

PlaceWhen you use itWho creates the context
AddDbContext in Program.csASP.NET Core apps (most common)Dependency injection
OnConfiguring inside the contextSmall console apps, quick testsYou, with new
External DbContextOptions passed to the constructorUnit tests, full controlYou, by hand

For web apps, the first row is the one you will use almost every time. Let us start there.

The AddDbContext way

In an ASP.NET Core app, you register your context in Program.cs. You call AddDbContext and pass a small function that receives the options builder. Inside that function, you chain your settings.

var builder = WebApplication.CreateBuilder(args);
 
string connectionString = builder.Configuration
    .GetConnectionString("AppDb")!;
 
builder.Services.AddDbContext<AppDbContext>(options =>
{
    options.UseSqlServer(connectionString);
});
 
var app = builder.Build();

The options variable here is your DbContextOptionsBuilder. The UseSqlServer call is the most important line. It tells EF Core which provider to use and where the database lives. Each context must use one and only one provider.

The OnConfiguring way

In a small console app, you may not have dependency injection. There you can override OnConfiguring inside the context itself.

public class AppDbContext : DbContext
{
    protected override void OnConfiguring(
        DbContextOptionsBuilder options)
    {
        options.UseSqlite("Data Source=app.db");
    }
 
    public DbSet<Student> Students => Set<Student>();
}

This is simple, but the connection string is hard-coded. For real apps, prefer AddDbContext so settings come from configuration files and secrets.

The constructor way

The third way passes a ready-made DbContextOptions to the constructor. This is the cleanest for testing because you control everything.

var options = new DbContextOptionsBuilder<AppDbContext>()
    .UseInMemoryDatabase("TestDb")
    .Options;
 
using var context = new AppDbContext(options);

Your context needs a constructor that accepts these options and hands them to the base class.

Three ways to give options to a DbContext

AddDbContext
OnConfiguring
Constructor
DbContextOptions

Steps

1

AddDbContext

DI builds options

2

OnConfiguring

Context builds its own

3

Constructor

You pass options in

4

DbContextOptions

Final recipe card

All roads end at the same DbContextOptions object.

The provider call and its inner options

The Use... call, like UseSqlServer or UseNpgsql, does two things. First, it picks the provider. Second, it accepts a second function for provider-specific settings. This is where some of the most useful options live, and beginners often miss this inner door.

builder.Services.AddDbContext<AppDbContext>(options =>
{
    options.UseSqlServer(connectionString, sql =>
    {
        sql.EnableRetryOnFailure();
        sql.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
        sql.CommandTimeout(60);
    });
});

Notice the two levels. The outer options is the general builder. The inner sql is the SQL Server builder. Retries and query splitting are provider settings, so they belong inside the inner function.

Options come in two layers: the general builder and the provider builder.

The options you will actually use

Let us go through the settings a beginner meets most often. We will explain what each one does in plain words.

EnableRetryOnFailure (connection resiliency)

Cloud databases sometimes drop a connection for a second. The fix should not be "the user sees an error." EnableRetryOnFailure tells EF Core to try the command again a few times before giving up. Microsoft recommends this when connecting to Azure SQL.

Behind this is an execution strategy. When you turn on retries, EF Core wraps each operation so it can repeat it. One catch: if you start a transaction by hand, you must run it through the strategy, because a retry must replay the whole transaction, not half of it.

EnableSensitiveDataLogging

By default, when EF Core logs a query, it hides the real values. You see @p0 instead of the actual name or password. That keeps logs safe. EnableSensitiveDataLogging removes that shield and shows the real values. This is wonderful while debugging on your laptop, because you can see exactly what was sent. But it can leak private data, so keep it off in production.

EnableDetailedErrors

Normally EF Core gives short error messages to stay fast. EnableDetailedErrors adds more detail, especially when a value fails to read into a property. Like sensitive logging, use it in development.

Query splitting behavior

When you load a parent and several child collections together, a single SQL query can blow up into a huge result with repeated rows. This is called cartesian explosion. Setting UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery) makes EF Core load each collection with its own query instead. You can also choose per query with AsSplitQuery() or AsSingleQuery().

Command timeout

CommandTimeout sets how many seconds EF Core waits for a single command before it gives up. Raise it for heavy reports; keep it small for quick web pages.

Here is a quick table that maps each option to where it belongs and when to use it.

OptionLayerUse it for
UseSqlServer / UseNpgsqlOuter builderPicking the provider
EnableRetryOnFailureProvider builderCloud / flaky networks
EnableSensitiveDataLoggingOuter builderLocal debugging only
EnableDetailedErrorsOuter builderLocal debugging only
UseQuerySplittingBehaviorProvider builderMany child collections
LogToOuter builderSeeing the SQL in console

Seeing the SQL with LogTo

A great way to learn EF Core is to watch the SQL it writes. The LogTo method sends logs to any function you like, such as the console.

builder.Services.AddDbContext<AppDbContext>(options =>
{
    options.UseSqlServer(connectionString)
           .LogTo(Console.WriteLine)
           .EnableSensitiveDataLogging(); // dev only
});

Now every query prints to the console. Combine it with EnableSensitiveDataLogging during development and you can see the exact values too. Turn both off before you ship.

Lifetimes: how long does a context live?

A DbContext is meant to be short-lived. Create one, do a unit of work, then dispose it. In ASP.NET Core, AddDbContext registers the context with a scoped lifetime by default. That means one context per web request, and it is disposed when the request ends.

This matters because a DbContext is not thread-safe. You should never share one context across parallel tasks. One request, one context, used one step at a time.

Scoped DbContext lifetime in a web request

Request in
Create context
Query and save
Dispose context

Steps

1

Request in

User hits an endpoint

2

Create context

DI makes a new one

3

Query and save

Do the work

4

Dispose context

Cleaned up at end

A fresh context is born and dies with each request.

Pooling: reusing contexts for speed

Creating a context has a small cost. On a very busy site, that cost adds up. AddDbContextPool keeps a small pool of context instances. Instead of building a fresh one each request, EF Core hands out a reset one from the pool, then takes it back when the request ends.

builder.Services.AddDbContextPool<AppDbContext>(options =>
{
    options.UseSqlServer(connectionString);
});

Pooling can speed up high-traffic apps. But there is a rule: do not store per-request data inside your context, like the current user's id in a field. Because the same instance is reused, that data could leak into the next request. Keep the context clean and stateless.

With pooling, contexts are borrowed and returned instead of rebuilt.

A complete, sensible setup

Let us put the good parts together. This setup reads the connection string from configuration, enables retries for the cloud, and turns on rich logging only when running in development.

var builder = WebApplication.CreateBuilder(args);
 
string connectionString = builder.Configuration
    .GetConnectionString("AppDb")!;
 
builder.Services.AddDbContext<AppDbContext>(options =>
{
    options.UseSqlServer(connectionString, sql =>
    {
        sql.EnableRetryOnFailure();
        sql.CommandTimeout(60);
    });
 
    if (builder.Environment.IsDevelopment())
    {
        options.LogTo(Console.WriteLine)
               .EnableSensitiveDataLogging()
               .EnableDetailedErrors();
    }
});
 
var app = builder.Build();

This is a friendly default for a real app. The risky logging is fenced behind a development check, so production stays safe and quiet.

Common beginner mistakes

A few traps catch almost everyone at the start. Knowing them now saves hours later.

  • Putting retry on the outer builder. EnableRetryOnFailure is a provider option. It goes inside the UseSqlServer function, not on the outer options.
  • Leaving sensitive logging on in production. It can leak private values. Guard it with an environment check.
  • Sharing one context across threads. A context is not thread-safe. Use one per request, one step at a time.
  • Storing per-request state in a pooled context. With pooling, that state can bleed into the next request. Keep the context stateless.
  • Configuring the provider twice. If you call UseSqlServer in both OnConfiguring and AddDbContext, you can get confusing behavior. Pick one place.

How EF Core reads your options at startup

It helps to picture the flow. When your app starts, dependency injection calls your lambda, which fills the builder. EF Core freezes that into a DbContextOptions. Then, for each request, the context is created using that frozen card.

From startup configuration to a working context per request.

This is why your options feel "global." You set the card once, and every request follows it. Only per-query helpers like AsSplitQuery() let you override a setting for a single query.

A note on naming and versions

You may see this topic called "Entity Framework extensions options." The settings here are the built-in DbContextOptionsBuilder options that ship with EF Core itself. They are not a separate paid product. As of 2026, .NET 10 is the long-term support release and C# 14 has shipped, but these options have stayed stable across many EF Core versions, so what you learn here keeps working.

If you later add third-party libraries that bring their own Use... extension methods, they plug into the very same builder. The pattern you learned does not change: pick a provider, then chain options.

Quick recap

  • The DbContext is your chai maker; the options are its recipe card, written once at startup.
  • The DbContextOptionsBuilder is the object you use to write that card.
  • The three places to set options are AddDbContext (web apps), OnConfiguring (small apps), and the constructor (tests).
  • The provider call like UseSqlServer has an inner function for provider settings such as retries and query splitting.
  • EnableRetryOnFailure adds connection resiliency for the cloud; it lives inside the provider function.
  • EnableSensitiveDataLogging and EnableDetailedErrors are for development only; never ship them on.
  • LogTo(Console.WriteLine) lets you watch the real SQL while you learn.
  • A context is scoped and not thread-safe: one per request, used one step at a time.
  • AddDbContextPool reuses contexts for speed, but keep them stateless.
  • Guard risky logging behind an environment check so production stays safe.

References and further reading

Related Posts