Skip to content
Docs Try Aspire
Docs Try

Entity Framework Core integrations overview

.NET / Entity Framework Core logo

Entity Framework Core (EF Core) is Microsoft’s modern, open-source object-relational mapper (O/RM) for .NET. Aspire provides EF Core client integrations for the most popular relational and document databases, making it straightforward to register a pooled DbContext, enable health checks and distributed tracing, and configure connection-retry resiliency — all from the consuming project’s Program.cs. Because EF Core is a .NET library, all client-side registration code is C# only; the AppHost hosting integrations for the underlying databases support both C# and TypeScript.

Aspire’s EF Core integrations build on standard EF Core patterns and extend them for cloud-native microservices:

  • Centrally managed connections — Database resources (containers or connection strings) are declared once in the AppHost project and forwarded as named references to any consuming service.
  • Pooled DbContext by defaultAdd<DatabaseSystem>DbContext registers a context pool automatically, reducing per-request allocation overhead.
  • Built-in observability — Health checks, OpenTelemetry distributed tracing, and connection-retry resiliency are enabled out of the box.
  • Enrichment API — When you need custom AddDbContextPool configuration, call Enrich<DatabaseSystem>DbContext afterwards to layer Aspire’s defaults on top of your own setup.
  • Migrations support — Run EF Core schema migrations at startup or publish them as container images for deployment. See Apply EF Core migrations in Aspire.
  • .NET-only client code — EF Core runs inside consuming .NET services; all DbContext registration code is C#.

Each database has its own Aspire EF Core client integration package. Select an integration to get started:

EF Core is a client integration — it runs inside consuming .NET services, not in the AppHost. The AppHost uses separate hosting integrations to model and launch the database container or cloud resource. The following hosting integrations pair with the EF Core client integrations listed above:

DatabaseHosting integration
PostgreSQLPostgreSQL hosting
SQL ServerSQL Server hosting
MySQLMySQL hosting
MongoDBMongoDB hosting
OracleOracle hosting
Azure Cosmos DBAzure Cosmos DB hosting
Azure SQLAzure SQL hosting

O/RMs create a model that maps to the schema and relationships defined in the database. In EF Core the model consists of:

  • A set of entity classes, each of which represents a table in the database.
  • A context class that represents the whole database and inherits from Microsoft.EntityFrameworkCore.DbContext.

An entity class might look like this:

C# — SupportTicket.cs
using System.ComponentModel.DataAnnotations;
namespace SupportDeskProject.Data;
public sealed class SupportTicket
{
public int Id { get; set; }
[Required]
public string Title { get; set; } = string.Empty;
[Required]
public string Description { get; set; } = string.Empty;
}

This entity class represents a database table with three columns: Id, Title, and Description.

A context class looks like this:

C# — TicketContext.cs
using Microsoft.EntityFrameworkCore;
namespace SupportDeskProject.Data;
public class TicketContext(DbContextOptions options) : DbContext(options)
{
public DbSet<SupportTicket> Tickets => Set<SupportTicket>();
}

This context represents a database with a single table of support tickets. An instance of the context is usually created for each unit of work in the database and disposed once that work is complete.

Once you’ve created a model, you can use LINQ to query it:

C# — Querying data with EF Core
using (var db = new TicketContext())
{
var tickets = await db.Tickets
.Where(t => t.Title == "Unable to log on")
.OrderBy(t => t.Description)
.ToListAsync();
}
A diagram showing how Aspire utilizes EF Core to interact with a database.

To use EF Core in your microservice with Aspire:

  1. Define the EF Core model with entity classes and a context class in your service project.
  2. Register the context using an Aspire Add<DatabaseSystem>DbContext extension method in Program.cs, passing the connection name used in the AppHost.
  3. Inject and use the context via constructor injection in your service classes, exactly as you would in any EF Core app.

Both defining the EF Core model and querying the database are identical in Aspire projects to any other EF Core application. Only the context registration step differs, which is where Aspire adds health checks, tracing, and resiliency.

Select a database to view database-specific examples

In EF Core, a context is a class used to interact with the database. Contexts inherit from the Microsoft.EntityFrameworkCore.DbContext class. They provide access to the database through properties of type DbSet<T>, where each DbSet represents a table or collection of entities in the database. The context also manages database connections, tracks changes to entities, and handles operations like saving data and executing queries.

The Aspire EF Core client integrations each include extension methods named Add<DatabaseSystem>DbContext, where <DatabaseSystem> is the name identifying the database product you’re using. For example, the SQL Server EF Core client integration exposes AddSqlServerDbContext and the PostgreSQL client integration exposes AddNpgsqlDbContext.

These Aspire Add*DbContext methods:

  • Check that a context of the same type isn’t already registered in the dependency injection (DI) container.
  • Resolve the connection string from the application configuration using the name you pass to the method. This name must match the resource name used in the AppHost project.
  • Apply any DbContext options you provide.
  • Register the specified DbContext in the DI container with context pooling enabled.
  • Apply the recommended defaults unless you’ve disabled them via the Aspire EF Core settings:
    • Enable tracing.
    • Enable health checks.
    • Enable connection resiliency.

Use these methods when you want a simple way to create a context and don’t yet need advanced EF Core customization.

C# — Program.cs
builder.AddSqlServerDbContext<ExampleDbContext>(
connectionName: "database");
C# — Program.cs
builder.AddNpgsqlDbContext<ExampleDbContext>(
connectionName: "database");
C# — Program.cs
builder.AddOracleDatabaseDbContext<ExampleDbContext>(
connectionName: "database");
C# — Program.cs
builder.AddMySqlDbContext<ExampleDbContext>(
connectionName: "database");

You obtain the ExampleDbContext object from the DI container in the same way as for any other service:

C# — ExampleService.cs
public class ExampleService(ExampleDbContext context)
{
// Use context...
}

Alternatively, you can add a context to the DI container using the standard EF Core AddDbContextPool method, as commonly used in non-Aspire projects:

C# — Program.cs
builder.Services.AddDbContextPool<ExampleDbContext>(options =>
{
var connectionString = builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.");
options.UseSqlServer(connectionString);
});
C# — Program.cs
builder.Services.AddDbContextPool<ExampleDbContext>(options =>
{
var connectionString = builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.");
options.UseNpgsql(connectionString);
});
C# — Program.cs
builder.Services.AddDbContextPool<ExampleDbContext>(options =>
{
var connectionString = builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.");
options.UseOracle(connectionString);
});
C# — Program.cs
builder.Services.AddDbContextPool<ExampleDbContext>(options =>
{
var connectionString = builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.");
options.UseMySql(connectionString);
});

You have more flexibility when you create the context this way, for example:

By default, a context configured this way doesn’t include Aspire features such as telemetry and health checks. To add those features, each Aspire EF Core client integration includes a method named Enrich<DatabaseSystem>DbContext. These enrich methods:

  • Apply an EF Core settings object, if you passed one.
  • Configure connection retry settings.
  • Apply the recommended defaults unless you’ve disabled them via the Aspire EF Core settings:
    • Enable tracing.
    • Enable health checks.
    • Enable connection resiliency.
C# — Program.cs
builder.EnrichSqlServerDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
C# — Program.cs
builder.EnrichNpgsqlDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
C# — Program.cs
builder.EnrichOracleDatabaseDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
C# — Program.cs
builder.EnrichMySqlDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});

Obtain the context from the DI container using the same constructor-injection pattern:

C# — ExampleService.cs
public class ExampleService(ExampleDbContext context)
{
// Use context...
}

EF Core interceptors allow developers to hook into and modify database operations at various points during query and command execution. You can use them to log, modify, or suppress operations. Your interceptor must implement one or more interfaces from Microsoft.EntityFrameworkCore.Diagnostics.

Interceptors that depend on DI services are not supported by the Aspire Add<DatabaseSystem>DbContext methods. Use the EF Core AddDbContextPool method and call AddInterceptors in the options builder:

C# — Program.cs
builder.Services.AddDbContextPool<ExampleDbContext>((serviceProvider, options) =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("database"));
options.AddInterceptors(serviceProvider.GetRequiredService<ExampleInterceptor>());
});
builder.EnrichSqlServerDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
C# — Program.cs
builder.Services.AddDbContextPool<ExampleDbContext>((serviceProvider, options) =>
{
options.UseNpgsql(builder.Configuration.GetConnectionString("database"));
options.AddInterceptors(serviceProvider.GetRequiredService<ExampleInterceptor>());
});
builder.EnrichNpgsqlDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
C# — Program.cs
builder.Services.AddDbContextPool<ExampleDbContext>((serviceProvider, options) =>
{
options.UseOracle(builder.Configuration.GetConnectionString("database"));
options.AddInterceptors(serviceProvider.GetRequiredService<ExampleInterceptor>());
});
builder.EnrichOracleDatabaseDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
C# — Program.cs
builder.Services.AddDbContextPool<ExampleDbContext>((serviceProvider, options) =>
{
options.UseMySql(builder.Configuration.GetConnectionString("database"));
options.AddInterceptors(serviceProvider.GetRequiredService<ExampleInterceptor>());
});
builder.EnrichMySqlDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});

Use EF Core with dynamic connection strings in Aspire

Section titled “Use EF Core with dynamic connection strings in Aspire”

Most microservices always connect to the same database with the same credentials, so they always use the same connection string. However, you may need to change the connection string per request, for example:

  • Multi-tenant services that route to a different database per customer.
  • Services that authenticate with a different database user account per customer.

For these requirements, formulate a dynamic connection string at runtime, then register the context manually and enrich it:

C# — Program.cs
var connectionStringTemplate = builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.");
var connectionString = connectionStringTemplate.Replace("{DatabaseName}", "ContosoDatabase");
builder.Services.AddDbContext<ExampleDbContext>(options =>
options.UseSqlServer(connectionString));
builder.EnrichSqlServerDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
C# — Program.cs
var connectionStringTemplate = builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.");
var connectionString = connectionStringTemplate.Replace("{DatabaseName}", "ContosoDatabase");
builder.Services.AddDbContext<ExampleDbContext>(options =>
options.UseNpgsql(connectionString));
builder.EnrichNpgsqlDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
C# — Program.cs
var connectionStringTemplate = builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.");
var connectionString = connectionStringTemplate.Replace("{DatabaseName}", "ContosoDatabase");
builder.Services.AddDbContext<ExampleDbContext>(options =>
options.UseOracle(connectionString));
builder.EnrichOracleDatabaseDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
C# — Program.cs
var connectionStringTemplate = builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.");
var connectionString = connectionStringTemplate.Replace("{DatabaseName}", "ContosoDatabase");
builder.Services.AddDbContext<ExampleDbContext>(options =>
options.UseMySql(connectionString));
builder.EnrichMySqlDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});

The preceding code replaces the {DatabaseName} placeholder in the connection string with ContosoDatabase at runtime before creating and enriching the context.

An EF Core context is designed for a single unit of work. In many ASP.NET Core applications, each HTTP request maps to one unit of work, so you can register a pooled context tied to the request lifetime using AddDbContextPool paired with Enrich<DatabaseSystem>DbContext.

Other application types, such as ASP.NET Core Blazor, use a different dependency-injection scope and may need multiple units of work within a single request. In those cases, register a context factory using AddPooledDbContextFactory:

C# — Program.cs
builder.Services.AddPooledDbContextFactory<ExampleDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.")));
builder.EnrichSqlServerDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
C# — Program.cs
builder.Services.AddPooledDbContextFactory<ExampleDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.")));
builder.EnrichNpgsqlDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
C# — Program.cs
builder.Services.AddPooledDbContextFactory<ExampleDbContext>(options =>
options.UseOracle(builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.")));
builder.EnrichOracleDatabaseDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});
C# — Program.cs
builder.Services.AddPooledDbContextFactory<ExampleDbContext>(options =>
options.UseMySql(builder.Configuration.GetConnectionString("database")
?? throw new InvalidOperationException("Connection string 'database' not found.")));
builder.EnrichMySqlDbContext<ExampleDbContext>(
configureSettings: settings =>
{
settings.DisableRetry = false;
settings.CommandTimeout = 30; // seconds
});

When you register a context factory, resolve an IDbContextFactory<T> from the DI container and create contexts from it explicitly:

C# — ExampleService.cs
public class ExampleService(IDbContextFactory<ExampleDbContext> contextFactory)
{
using (var context = contextFactory.CreateDbContext())
{
// Use context...
}
}

Contexts created from factories are not tied to an HTTP request lifetime and are not disposed automatically. Always dispose of them explicitly, as shown with the using block above.

In EF Core, a context is relatively inexpensive to create, but if your microservice creates contexts at high frequency you may observe overhead. Context pooling mitigates this: when a context is disposed it is reset and returned to a pool, so the next request reuses it rather than creating a new one.

In an Aspire consuming project, there are three ways to use context pooling:

  • Use Add<DatabaseSystem>DbContext — pools are created automatically by the Aspire method.

  • Call AddDbContextPool instead of AddDbContext:

    C# — Program.cs
    builder.Services.AddDbContextPool<ExampleDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("database")
    ?? throw new InvalidOperationException("Connection string 'database' not found.")));
    C# — Program.cs
    builder.Services.AddDbContextPool<ExampleDbContext>(options =>
    options.UseNpgsql(builder.Configuration.GetConnectionString("database")
    ?? throw new InvalidOperationException("Connection string 'database' not found.")));
    C# — Program.cs
    builder.Services.AddDbContextPool<ExampleDbContext>(options =>
    options.UseOracle(builder.Configuration.GetConnectionString("database")
    ?? throw new InvalidOperationException("Connection string 'database' not found.")));
    C# — Program.cs
    builder.Services.AddDbContextPool<ExampleDbContext>(options =>
    options.UseMySql(builder.Configuration.GetConnectionString("database")
    ?? throw new InvalidOperationException("Connection string 'database' not found.")));
  • Call AddPooledDbContextFactory instead of AddDbContextFactory:

    C# — Program.cs
    builder.Services.AddPooledDbContextFactory<ExampleDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("database")
    ?? throw new InvalidOperationException("Connection string 'database' not found.")));
    C# — Program.cs
    builder.Services.AddPooledDbContextFactory<ExampleDbContext>(options =>
    options.UseNpgsql(builder.Configuration.GetConnectionString("database")
    ?? throw new InvalidOperationException("Connection string 'database' not found.")));
    C# — Program.cs
    builder.Services.AddPooledDbContextFactory<ExampleDbContext>(options =>
    options.UseOracle(builder.Configuration.GetConnectionString("database")
    ?? throw new InvalidOperationException("Connection string 'database' not found.")));
    C# — Program.cs
    builder.Services.AddPooledDbContextFactory<ExampleDbContext>(options =>
    options.UseMySql(builder.Configuration.GetConnectionString("database")
    ?? throw new InvalidOperationException("Connection string 'database' not found.")));

Remember to call the appropriate Enrich<DatabaseSystem>DbContext method after using the last two approaches, as described above.