Skip to content
20 changes: 20 additions & 0 deletions src/Aspire.Hosting.EntityFrameworkCore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,26 @@ for your deployment flow:

Similarly you can use `PublishAsMigrationScript()` if you also want a raw SQL script produced.

#### Troubleshooting: design-time connection strings

Generating a migration bundle runs the EF Core design-time tooling, which creates
your `DbContext` to discover the model. Providers that parse the connection string
while the context is being created — for example the Azure Npgsql integration
(`EnrichAzureNpgsqlDbContext`) — fail at design time because no real connection
string is available, producing an error such as:

```
Unable to create a 'DbContext' of type 'AppDbContext'. The exception 'Format of the
initialization string does not conform to specification starting at index 0.' was
thrown while attempting to create an instance.
```

Guard the connection-string-dependent configuration with `EF.IsDesignTime` so EF
Core can build the model without a connection string. The bundle still receives the
real connection string at run time via the `--connection` argument Aspire injects.
See [docs/design-time-connection-strings.md](docs/design-time-connection-strings.md)
for a step-by-step example.

## Additional documentation

<!-- TODO: Update this to the EntityFrameworkCore-specific page once published, https://github.com/microsoft/aspire.dev/issues/536 -->
Comment thread
AndriySvyryd marked this conversation as resolved.
Outdated
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Handling design-time connection strings when publishing migration bundles
Comment thread
AndriySvyryd marked this conversation as resolved.
Outdated

When you generate a migration bundle during `aspire publish`
(`PublishAsMigrationBundle(...)`), Aspire runs the EF Core design-time tooling
(`dotnet ef migrations bundle`) against your startup project. EF Core has to
create an instance of your `DbContext` at design time to discover the model.

If your `DbContext` is configured with a provider that **parses the connection
string eagerly** while the context is being created — for example the Azure
Npgsql integration (`EnrichAzureNpgsqlDbContext`) — design-time creation fails
because the value injected for the connection string at publish time is a
placeholder/manifest expression rather than a real connection string. The error
looks like this:

```
✗ [ERR] Unable to create a 'DbContext' of type 'AppDbContext'. The exception
'Format of the initialization string does not conform to specification starting
at index 0.' was thrown while attempting to create an instance.
✗ Step 'api-migrations-generate-migration-bundle' failed.
```

A migration bundle does not need a real connection string at design time: the
bundle receives the connection string at **run time** via the `--connection`
argument that Aspire wires up automatically for the bundle container. So the fix
is to skip the connection-string parsing while EF Core is building the model at
design time.

## Steps to fix the project

EF Core exposes `EF.IsDesignTime`, which is `true` while the design-time tooling
(including the `migrations bundle` command) is running. Use it to call a
different `UseNpgsql` overload and to skip `EnrichAzureNpgsqlDbContext` at design
time.

Given a project that registers the `DbContext` like this:

```csharp
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

public static IHostApplicationBuilder AddAppData(this IHostApplicationBuilder builder)
{
builder.Services.AddPooledDbContextFactory<AppDbContext>((sp, options) =>
{
options.UseNpgsql(builder.Configuration.GetConnectionString("postgresdb"));
});

builder.EnrichAzureNpgsqlDbContext<AppDbContext>();

return builder;
}
```

1. Call the parameterless `UseNpgsql()` overload when `EF.IsDesignTime` is
`true`. This lets EF Core build the model without parsing a connection string.
2. Only call `EnrichAzureNpgsqlDbContext` when **not** at design time, because the
enrichment configures an Entra ID data source that parses the connection string.

```csharp
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

public static IHostApplicationBuilder AddAppData(this IHostApplicationBuilder builder)
{
builder.Services.AddPooledDbContextFactory<AppDbContext>((sp, options) =>
{
if (EF.IsDesignTime)
{
// At design time (e.g. when generating a migration bundle) no real
// connection string is available. Use the parameterless overload so
// EF Core can build the model without parsing a connection string.
options.UseNpgsql();
}
else
{
options.UseNpgsql(builder.Configuration.GetConnectionString("postgresdb"));
}
});

if (!EF.IsDesignTime)
{
// EnrichAzureNpgsqlDbContext configures an Entra ID data source that
// parses the connection string, which is not available at design time.
builder.EnrichAzureNpgsqlDbContext<AppDbContext>();
}

return builder;
}
```

With this change, `aspire publish` generates the migration bundle successfully,
and the deployed bundle still receives the real connection string at run time via
the `--connection` argument that Aspire injects into the bundle container.

> The same pattern applies to any provider that parses the connection string while
> the `DbContext` is being created (for example a custom `NpgsqlDataSource`). Guard
> the connection-string-dependent configuration with `EF.IsDesignTime`.