diff --git a/docs/database/azure-postgresql-entity-framework-integration.md b/docs/database/azure-postgresql-entity-framework-integration.md index d6095fd6ca..eca0f14f38 100644 --- a/docs/database/azure-postgresql-entity-framework-integration.md +++ b/docs/database/azure-postgresql-entity-framework-integration.md @@ -1,7 +1,7 @@ --- title: .NET Aspire Azure PostgreSQL Entity Framework Core integration description: Learn how to integrate Azure PostgreSQL with .NET Aspire applications, using both hosting and Entity Framework Core client integrations. -ms.date: 01/21/2025 +ms.date: 03/31/2025 uid: dotnet/aspire/azure-postgresql-entity-framework-integration --- @@ -17,8 +17,6 @@ uid: dotnet/aspire/azure-postgresql-entity-framework-integration ## Client integration -[!INCLUDE [postgresql-ef-client](includes/postgresql-ef-client.md)] - [!INCLUDE [azure-postgresql-ef-client](includes/azure-postgresql-ef-client.md)] ## See also diff --git a/docs/database/azure-postgresql-integration.md b/docs/database/azure-postgresql-integration.md index ce3733a0fe..79f4ba482b 100644 --- a/docs/database/azure-postgresql-integration.md +++ b/docs/database/azure-postgresql-integration.md @@ -1,7 +1,7 @@ --- title: .NET Aspire Azure PostgreSQL integration description: Learn how to integrate Azure PostgreSQL with .NET Aspire applications, using both hosting and client integrations. -ms.date: 01/21/2025 +ms.date: 03/31/2025 uid: dotnet/aspire/azure-postgresql-integration --- @@ -17,8 +17,6 @@ uid: dotnet/aspire/azure-postgresql-integration ## Client integration -[!INCLUDE [postgresql-client](includes/postgresql-client.md)] - [!INCLUDE [azure-postgresql-client](includes/azure-postgresql-client.md)] ## See also diff --git a/docs/database/includes/azure-postgresql-client.md b/docs/database/includes/azure-postgresql-client.md index c0c26f0027..e130f50753 100644 --- a/docs/database/includes/azure-postgresql-client.md +++ b/docs/database/includes/azure-postgresql-client.md @@ -2,42 +2,165 @@ ms.topic: include --- -### Add Azure authenticated Npgsql client - -By default, when you call `AddAzurePostgresFlexibleServer` in your PostgreSQL hosting integration, it configures [📦 Azure.Identity](https://www.nuget.org/packages/Azure.Identity) NuGet package to enable authentication: +To get started with the .NET Aspire Azure PostgreSQL client integration, install the [📦 Aspire.Azure.Npgsql](https://www.nuget.org/packages/Aspire.Azure.Npgsql) NuGet package in the client-consuming project, that is, the project for the application that uses the PostgreSQL client. The PostgreSQL client integration registers an [NpgsqlDataSource](https://www.npgsql.org/doc/api/Npgsql.NpgsqlDataSource.html) instance that you can use to interact with PostgreSQL. ### [.NET CLI](#tab/dotnet-cli) ```dotnetcli -dotnet add package Azure.Identity +dotnet add package Aspire.Azure.Npgsql ``` ### [PackageReference](#tab/package-reference) ```xml - ``` --- -The PostgreSQL connection can be consumed using the client integration and : + + +The PostgreSQL connection can be consumed using the client integration by calling the `AddAzureNpgsqlDataSource`: + +```csharp +builder.AddAzureNpgsqlDataSource(connectionName: "postgresdb"); +``` + +> [!TIP] +> The `connectionName` parameter must match the name used when adding the PostgreSQL server resource in the app host project. + +The preceding code snippet demonstrates how to use the `AddAzureNpgsqlDataSource` method to register an `NpgsqlDataSource` instance that uses Azure authentication ([Microsoft Entra ID](/azure/postgresql/flexible-server/concepts-azure-ad-authentication)). This `"postgresdb"` connection name corresponds to a connection string configuration value. + +After adding `NpgsqlDataSource` to the builder, you can get the `NpgsqlDataSource` instance using dependency injection. For example, to retrieve your data source object from an example service define it as a constructor parameter and ensure the `ExampleService` class is registered with the dependency injection container: + +```csharp +public class ExampleService(NpgsqlDataSource dataSource) +{ + // Use dataSource... +} +``` + +For more information on dependency injection, see [.NET dependency injection](/dotnet/core/extensions/dependency-injection). + +### Add keyed Azure Npgsql client + + + +There might be situations where you want to register multiple `NpgsqlDataSource` instances with different connection names. To register keyed Npgsql clients, call the `AddKeyedAzureNpgsqlDataSource` method: + +```csharp +builder.AddKeyedAzureNpgsqlDataSource(name: "sales_db"); +builder.AddKeyedAzureNpgsqlDataSource(name: "inventory_db"); +``` + +Then you can retrieve the `NpgsqlDataSource` instances using dependency injection. For example, to retrieve the connection from an example service: + +```csharp +public class ExampleService( + [FromKeyedServices("sales_db")] NpgsqlDataSource salesDataSource, + [FromKeyedServices("inventory_db")] NpgsqlDataSource inventoryDataSource) +{ + // Use data sources... +} +``` + +For more information on keyed services, see [.NET dependency injection: Keyed services](/dotnet/core/extensions/dependency-injection#keyed-services). + +#### Configuration + +The .NET Aspire Azure Npgsql integration provides multiple options to configure the database connection based on the requirements and conventions of your project. + +##### Use a connection string + +When using a connection string defined in the `ConnectionStrings` configuration section, you provide the name of the connection string when calling `AddAzureNpgsqlDataSource`: ```csharp -builder.AddNpgsqlDataSource( - "postgresdb", - configureDataSourceBuilder: (dataSourceBuilder) => +builder.AddAzureNpgsqlDataSource("postgresdb"); +``` + +The connection string is retrieved from the `ConnectionStrings` configuration section, for example, consider the following JSON configuration: + +```json { - if (string.IsNullOrEmpty(dataSourceBuilder.ConnectionStringBuilder.Password)) - { - var credentials = new DefaultAzureCredential(); - var tokenRequest = new TokenRequestContext(["https://ossrdbms-aad.database.windows.net/.default"]); - - dataSourceBuilder.UsePasswordProvider( - passwordProvider: _ => credentials.GetToken(tokenRequest).Token, - passwordProviderAsync: async (_, ct) => (await credentials.GetTokenAsync(tokenRequest, ct)).Token); + "ConnectionStrings": { + "postgresdb": "Host=myserver;Database=test" + } +} +``` + +For more information on how to configure the connection string, see the [Npgsql connection string documentation](https://www.npgsql.org/doc/connection-string-parameters.html). + +> [!NOTE] +> The username and password are automatically inferred from the credential provided in the settings. + +##### Use configuration providers + + + +The .NET Aspire Azure Npgsql integration supports . It loads the `AzureNpgsqlSettings` from configuration using the `Aspire:Azure:Npgsql` key. For example, consider the following _appsettings.json_ file that configures some of the available options: + +```json +{ + "Aspire": { + "Npgsql": { + "DisableHealthChecks": true, + "DisableTracing": true } -}); + } +} ``` -The preceding code snippet demonstrates how to use the class from the package to authenticate with [Microsoft Entra ID](/azure/postgresql/flexible-server/concepts-azure-ad-authentication) and retrieve a token to connect to the PostgreSQL database. The [UsePasswordProvider](https://www.npgsql.org/doc/api/Npgsql.NpgsqlDataSourceBuilder.html#Npgsql_NpgsqlDataSourceBuilder_UsePasswordProvider_System_Func_Npgsql_NpgsqlConnectionStringBuilder_System_String__System_Func_Npgsql_NpgsqlConnectionStringBuilder_System_Threading_CancellationToken_System_Threading_Tasks_ValueTask_System_String___) method is used to provide the token to the data source builder. +##### Use inline delegates + +You can configure settings in code, by passing the `Action configureSettings` delegate to set up some or all the options inline, for example to disable health checks from code: + +```csharp +builder.AddAzureNpgsqlDataSource( + "postgresdb", + settings => settings.DisableHealthChecks = true); +``` + + + +Use the `AzureNpgsqlSettings.Credential` property to establish a connection. If no credential is configured, the is used. When the connection string contains a username and password, the credential is ignored. + +[!INCLUDE [client-integration-health-checks](../../includes/client-integration-health-checks.md)] + +- Adds the [`NpgSqlHealthCheck`](https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/blob/master/src/HealthChecks.NpgSql/NpgSqlHealthCheck.cs), which verifies that commands can be successfully executed against the underlying Postgres database. +- Integrates with the `/health` HTTP endpoint, which specifies all registered health checks must pass for app to be considered ready to accept traffic + +[!INCLUDE [integration-observability-and-telemetry](../../includes/integration-observability-and-telemetry.md)] + +#### Logging + +The .NET Aspire PostgreSQL integration uses the following log categories: + +- `Npgsql.Connection` +- `Npgsql.Command` +- `Npgsql.Transaction` +- `Npgsql.Copy` +- `Npgsql.Replication` +- `Npgsql.Exception` + +#### Tracing + +The .NET Aspire PostgreSQL integration will emit the following tracing activities using OpenTelemetry: + +- `Npgsql` + +#### Metrics + +The .NET Aspire PostgreSQL integration will emit the following metrics using OpenTelemetry: + +- Npgsql: + - `ec_Npgsql_bytes_written_per_second` + - `ec_Npgsql_bytes_read_per_second` + - `ec_Npgsql_commands_per_second` + - `ec_Npgsql_total_commands` + - `ec_Npgsql_current_commands` + - `ec_Npgsql_failed_commands` + - `ec_Npgsql_prepared_commands_ratio` + - `ec_Npgsql_connection_pools` + - `ec_Npgsql_multiplexing_average_commands_per_batch` + - `ec_Npgsql_multiplexing_average_write_time_per_batch` diff --git a/docs/database/includes/azure-postgresql-ef-client.md b/docs/database/includes/azure-postgresql-ef-client.md index 6cedb1b170..a97ca201c6 100644 --- a/docs/database/includes/azure-postgresql-ef-client.md +++ b/docs/database/includes/azure-postgresql-ef-client.md @@ -2,66 +2,263 @@ ms.topic: include --- -### Add Azure authenticated Npgsql client - -By default, when you call `AddAzurePostgresFlexibleServer` in your PostgreSQL hosting integration, it requires [📦 Azure.Identity](https://www.nuget.org/packages/Azure.Identity) NuGet package to enable authentication: +To get started with the .NET Aspire PostgreSQL Entity Framework Core client integration, install the [📦 Aspire.Azure.Npgsql.EntityFrameworkCore.PostgreSQL](https://www.nuget.org/packages/) NuGet package in the client-consuming project, that is, the project for the application that uses the PostgreSQL client. The .NET Aspire PostgreSQL Entity Framework Core client integration registers your desired `DbContext` subclass instances that you can use to interact with PostgreSQL. ### [.NET CLI](#tab/dotnet-cli) ```dotnetcli -dotnet add package Azure.Identity +dotnet add package Aspire.Azure.Npgsql.EntityFrameworkCore.PostgreSQL ``` ### [PackageReference](#tab/package-reference) ```xml - ``` --- -The PostgreSQL connection can be consumed using the client integration and . + + +The PostgreSQL connection can be consumed using the client integration by calling the `AddAzureNpgsqlDataSource`: + +```csharp +builder.AddAzureNpgsqlDbContext(connectionName: "postgresdb"); +``` + +> [!TIP] +> The `connectionName` parameter must match the name used when adding the PostgreSQL server resource in the app host project. -The following code snippets demonstrate how to use the class from the package to authenticate with [Microsoft Entra ID](/azure/postgresql/flexible-server/concepts-azure-ad-authentication) and retrieve a token to connect to the PostgreSQL database. The [UsePasswordProvider](https://www.npgsql.org/doc/api/Npgsql.NpgsqlDataSourceBuilder.html#Npgsql_NpgsqlDataSourceBuilder_UsePasswordProvider_System_Func_Npgsql_NpgsqlConnectionStringBuilder_System_String__System_Func_Npgsql_NpgsqlConnectionStringBuilder_System_Threading_CancellationToken_System_Threading_Tasks_ValueTask_System_String___) method is used to provide the token to the data source builder. +The preceding code snippet demonstrates how to use the `AddAzureNpgsqlDbContext` method to register an `YourDbContext` (that's [pooled for performance](/ef/core/performance/advanced-performance-topics)) instance that uses Azure authentication ([Microsoft Entra ID](/azure/postgresql/flexible-server/concepts-azure-ad-authentication)). This `"postgresdb"` connection name corresponds to a connection string configuration value. -### EF Core version 8 +After adding `YourDbContext` to the builder, you can get the `YourDbContext` instance using dependency injection. For example, to retrieve your data source object from an example service define it as a constructor parameter and ensure the `ExampleService` class is registered with the dependency injection container: ```csharp -var dsBuilder = new NpgsqlDataSourceBuilder(builder.Configuration.GetConnectionString("postgresdb")); -if (string.IsNullOrEmpty(dsBuilder.ConnectionStringBuilder.Password)) +public class ExampleService(YourDbContext context) { - var credentials = new DefaultAzureCredential(); - var tokenRequest = new TokenRequestContext(["https://ossrdbms-aad.database.windows.net/.default"]); + // Use context... +} +``` + +For more information on dependency injection, see [.NET dependency injection](/dotnet/core/extensions/dependency-injection). + +### Enrich an Npgsql database context + +You may prefer to use the standard Entity Framework method to obtain a database context and add it to the dependency injection container: + +```csharp +builder.Services.AddDbContext(options => + options.UseNpgsql(builder.Configuration.GetConnectionString("postgresdb") + ?? throw new InvalidOperationException("Connection string 'postgresdb' not found."))); +``` + +> [!NOTE] +> The connection string name that you pass to the method must match the name used when adding the PostgreSQL server resource in the app host project. For more information, see [Add PostgreSQL server resource](#add-postgresql-server-resource). + +You have more flexibility when you create the database context in this way, for example: + +- You can reuse existing configuration code for the database context without rewriting it for .NET Aspire. +- You can use Entity Framework Core interceptors to modify database operations. +- You can choose not to use Entity Framework Core context pooling, which may perform better in some circumstances. + +If you use this method, you can enhance the database context with .NET Aspire-style retries, health checks, logging, and telemetry features by calling the `EnrichAzureNpgsqlDbContext` method: + +```csharp +builder.EnrichAzureNpgsqlDbContext( + configureSettings: settings => + { + settings.DisableRetry = false; + settings.CommandTimeout = 30; + }); +``` + +The `settings` parameter is an instance of the `AzureNpgsqlEntityFrameworkCorePostgreSQLSettings` class. - dsBuilder.UsePasswordProvider( - passwordProvider: _ => credentials.GetToken(tokenRequest).Token, - passwordProviderAsync: async (_, ct) => (await credentials.GetTokenAsync(tokenRequest, ct)).Token); + + +You might also need to configure specific options of Npgsql, or register a in other ways. In this case, you do so by calling the `EnrichAzureNpgsqlDbContext` extension method, as shown in the following example: + +```csharp +var connectionString = builder.Configuration.GetConnectionString("postgresdb"); + +builder.Services.AddDbContextPool( + dbContextOptionsBuilder => dbContextOptionsBuilder.UseNpgsql(connectionString)); + +builder.EnrichAzureNpgsqlDbContext(); +``` + +#### Configuration + +The .NET Aspire Azure PostgreSQL EntityFrameworkCore Npgsql integration provides multiple options to configure the database connection based on the requirements and conventions of your project. + +##### Use a connection string + +When using a connection string defined in the `ConnectionStrings` configuration section, you provide the name of the connection string when calling `AddAzureNpgsqlDataSource`: + +```csharp +builder.AddAzureNpgsqlDbContext("postgresdb"); +``` + +The connection string is retrieved from the `ConnectionStrings` configuration section, for example, consider the following JSON configuration: + +```json +{ + "ConnectionStrings": { + "postgresdb": "Host=myserver;Database=test" + } } +``` -builder.AddNpgsqlDbContext( - "postgresdb", - configureDbContextOptions: (options) => options.UseNpgsql(dsBuilder.Build())); +For more information on how to configure the connection string, see the [Npgsql connection string documentation](https://www.npgsql.org/doc/connection-string-parameters.html). + +> [!NOTE] +> The username and password are automatically inferred from the credential provided in the settings. + +##### Use configuration providers + + + +The .NET Aspire Azure PostgreSQL EntityFrameworkCore Npgsql integration supports . It loads the `AzureNpgsqlEntityFrameworkCorePostgreSQLSettings` from configuration using the `Aspire:Npgsql:EntityFrameworkCore:PostgreSQL` key. For example, consider the following _appsettings.json_ file that configures some of the available options: + +```json +{ + "Aspire": { + "Npgsql": { + "EntityFrameworkCore": { + "PostgreSQL": { + "DisableHealthChecks": true, + "DisableTracing": true + } + } + } + } +} ``` -### EF Core version 9+ +##### Use inline delegates -With EF Core version 9, you can use the `ConfigureDataSource` method to configure the `NpgsqlDataSourceBuilder` that's used by the integration instead of building one outside of the integration and passing it in. +You can configure settings in code, by passing the `Action configureSettings` delegate to set up some or all the options inline, for example to disable health checks from code: ```csharp -builder.AddNpgsqlDbContext( +builder.AddAzureNpgsqlDbContext( "postgresdb", - configureDbContextOptions: (options) => options.UseNpgsql(npgsqlOptions => - npgsqlOptions.ConfigureDataSource(dsBuilder => - { - if (string.IsNullOrEmpty(dsBuilder.ConnectionStringBuilder.Password)) - { - var credentials = new DefaultAzureCredential(); - var tokenRequest = new TokenRequestContext(["https://ossrdbms-aad.database.windows.net/.default"]); - - dsBuilder.UsePasswordProvider( - passwordProvider: _ => credentials.GetToken(tokenRequest).Token, - passwordProviderAsync: async (_, ct) => (await credentials.GetTokenAsync(tokenRequest, ct)).Token); - } - }))); + settings => settings.DisableHealthChecks = true); +``` + +Alternatively, you can use the `EnrichAzureNpgsqlDbContext` extension method to configure the settings: + +```csharp +builder.EnrichAzureNpgsqlDbContext( + settings => settings.DisableHealthChecks = true); +``` + + + +Use the `AzureNpgsqlEntityFrameworkCorePostgreSQLSettings.Credential` property to establish a connection. If no credential is configured, the is used. + +When the connection string contains a username and password, the credential is ignored. + +##### Troubleshooting + +In the rare case that the `Username` property isn't provided and the integration can't detect it using the application's Managed Identity, Npgsql throws an exception with a message similar to the following: + +> Npgsql.PostgresException (0x80004005): 28P01: password authentication failed for user ... + +In this case you can configure the `Username` property in the connection string and use `EnrichAzureNpgsqlDbContext`, passing the connection string in `UseNpgsql`: + +```csharp +builder.Services.AddDbContextPool( + options => options.UseNpgsql(newConnectionString)); + +builder.EnrichAzureNpgsqlDbContext(); ``` + +#### Configure multiple DbContext classes + +If you want to register more than one with different configuration, you can use `$"Aspire:Npgsql:EntityFrameworkCore:PostgreSQL:{typeof(TContext).Name}"` configuration section name. The json configuration would look like: + +```json +{ + "Aspire": { + "Npgsql": { + "EntityFrameworkCore": { + "PostgreSQL": { + "ConnectionString": "", + "DisableHealthChecks": true, + "DisableTracing": true, + "AnotherDbContext": { + "ConnectionString": "", + "DisableTracing": false + } + } + } + } + } +} +``` + +Then calling the method with `AnotherDbContext` type parameter would load the settings from `Aspire:Npgsql:EntityFrameworkCore:PostgreSQL:AnotherDbContext` section. + +```csharp +builder.AddAzureNpgsqlDbContext(); +``` + +[!INCLUDE [client-integration-health-checks](../../includes/client-integration-health-checks.md)] + +By default, the .NET Aspire PostgreSQL Entity Framework Core integrations handles the following: + +- Adds the [`DbContextHealthCheck`](https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/blob/master/src/HealthChecks.NpgSql/NpgSqlHealthCheck.cs), which calls EF Core's method. The name of the health check is the name of the `TContext` type. +- Integrates with the `/health` HTTP endpoint, which specifies all registered health checks must pass for app to be considered ready to accept traffic + +[!INCLUDE [integration-observability-and-telemetry](../../includes/integration-observability-and-telemetry.md)] + +#### Logging + +The .NET Aspire PostgreSQL Entity Framework Core integration uses the following Log categories: + +- `Microsoft.EntityFrameworkCore.ChangeTracking` +- `Microsoft.EntityFrameworkCore.Database.Command` +- `Microsoft.EntityFrameworkCore.Database.Connection` +- `Microsoft.EntityFrameworkCore.Database.Transaction` +- `Microsoft.EntityFrameworkCore.Migrations` +- `Microsoft.EntityFrameworkCore.Infrastructure` +- `Microsoft.EntityFrameworkCore.Migrations` +- `Microsoft.EntityFrameworkCore.Model` +- `Microsoft.EntityFrameworkCore.Model.Validation` +- `Microsoft.EntityFrameworkCore.Query` +- `Microsoft.EntityFrameworkCore.Update` + +#### Tracing + +The .NET Aspire PostgreSQL Entity Framework Core integration will emit the following tracing activities using OpenTelemetry: + +- `Npgsql` + +#### Metrics + +The .NET Aspire PostgreSQL Entity Framework Core integration will emit the following metrics using OpenTelemetry: + +- Microsoft.EntityFrameworkCore: + - `ec_Microsoft_EntityFrameworkCore_active_db_contexts` + - `ec_Microsoft_EntityFrameworkCore_total_queries` + - `ec_Microsoft_EntityFrameworkCore_queries_per_second` + - `ec_Microsoft_EntityFrameworkCore_total_save_changes` + - `ec_Microsoft_EntityFrameworkCore_save_changes_per_second` + - `ec_Microsoft_EntityFrameworkCore_compiled_query_cache_hit_rate` + - `ec_Microsoft_Entity_total_execution_strategy_operation_failures` + - `ec_Microsoft_E_execution_strategy_operation_failures_per_second` + - `ec_Microsoft_EntityFramew_total_optimistic_concurrency_failures` + - `ec_Microsoft_EntityF_optimistic_concurrency_failures_per_second` + +- Npgsql: + - `ec_Npgsql_bytes_written_per_second` + - `ec_Npgsql_bytes_read_per_second` + - `ec_Npgsql_commands_per_second` + - `ec_Npgsql_total_commands` + - `ec_Npgsql_current_commands` + - `ec_Npgsql_failed_commands` + - `ec_Npgsql_prepared_commands_ratio` + - `ec_Npgsql_connection_pools` + - `ec_Npgsql_multiplexing_average_commands_per_batch` + - `ec_Npgsql_multiplexing_average_write_time_per_batch` diff --git a/docs/database/includes/postgresql-app-host.md b/docs/database/includes/postgresql-app-host.md index 3144875bc3..d00aceb504 100644 --- a/docs/database/includes/postgresql-app-host.md +++ b/docs/database/includes/postgresql-app-host.md @@ -93,7 +93,7 @@ builder.AddProject() // After adding all resources, run the app... ``` -The preceding example creates a database named `app_db`. The script is executed when the database resource is created. The script is passed as a string to the `WithCreationScript` method, which is then executed in the context of the SQL Server resource. +The preceding example creates a database named `app_db`. The script is run when the database resource is created. The script is passed as a string to the `WithCreationScript` method, which is then run in the context of the PostgreSQL resource. > [!NOTE] > The connect to a database command (`\c`) isn't supported when using the creation script.