Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document new EF/Npgsql configuration experience #353

Merged
merged 1 commit into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 114 additions & 38 deletions conceptual/EFCore.PG/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,63 +19,139 @@ Below is a `.csproj` file for a console application that uses the Npgsql EF Core
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.3" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
</ItemGroup>
</Project>
```

## Defining a `DbContext`
## Defining a model and a `DbContext`

Let's say you want to store blogs and their posts in their database; you can model these as .NET types as follows:

```c#
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }

public List<Post> Posts { get; set; }
}

namespace ConsoleApp.PostgreSQL
public class Post
{
public class BloggingContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseNpgsql("Host=my_host;Database=my_db;Username=my_user;Password=my_pw");
}

public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }

public List<Post> Posts { get; set; }
}

public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }

public int BlogId { get; set; }
public Blog Blog { get; set; }
}
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }

public int BlogId { get; set; }
public Blog Blog { get; set; }
}
```

## Additional configuration for ASP.NET Core applications
You then define a `DbContext` type which you'll use to interact with the database:

### [OnConfiguring](#tab/onconfiguring)

Consult [this tutorial](https://docs.microsoft.com/en-us/aspnet/core/data/ef-rp/intro) for general information on how to make ASP.NET work with EF Core. For Npgsql specifically, simply place the following in your `ConfigureServices` method in `Startup.cs`:
Using `OnConfiguring()` to configure your context is the easiest way to get started, but is discouraged for most production applications:

```c#
public void ConfigureServices(IServiceCollection services)
public class BloggingContext : DbContext
{
// Other DI initializations
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }

services.AddDbContext<BloggingContext>(options =>
options.UseNpgsql(Configuration.GetConnectionString("BloggingContext")));
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseNpgsql("<connection string>");
}

// At the point where you need to perform a database operation:
using var context = new BloggingContext();
// Use the context...
```

### [DbContext pooling](#tab/context-pooling)

```c#
var dbContextFactory = new PooledDbContextFactory<BloggingContext>(
new DbContextOptionsBuilder<BloggingContext>()
.UseNpgsql("<connection string>")
.Options);

// At the point where you need to perform a database operation:
using var context = dbContextFactory.CreateDbContext();
// Use the context...
```

### [ASP.NET / DI](#tab/aspnet)

When using ASP.NET - or any application with dependency injection - the context instance will be injected into your code. Use the following to configure EF with your DI container:

```c#
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContextPool<BloggingContext>(opt =>
opt.UseNpgsql(builder.Configuration.GetConnectionString("BloggingContext")));

public class BloggingContext(DbContextOptions<BloggingContext> options) : DbContext(options)
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
```

***

For more information on getting started with EF, consult the [EF getting started documentation](https://learn.microsoft.com/en-us/ef/core/get-started/overview/first-app?tabs=netcore-cli).

## Additional Npgsql configuration

The Npgsql EF provider is built on top of the lower-level Npgsql ADO.NET provider ([docs](https://www.npgsql.org/doc/index.html)); these two separate components support various options you may want to configure.

If you're using EF 9.0 or above, the `UseNpgsql()` is a single point where you can configure everything related to Npgsql. For example:

```c#
builder.Services.AddDbContextPool<BloggingContext>(opt =>
opt.UseNpgsql(
builder.Configuration.GetConnectionString("BloggingContext"),
o => o
.SetPostgresVersion(13, 0)
.UseNodaTime()
.MapEnum<Mood>("mood")));
```

The above configures the EF provider to produce SQL for PostgreSQL version 13 (avoiding newer incompatible features), adds a plugin allowing use of NodaTime for date/time type mapping, and maps a .NET enum type. Note that the last two also require configuration at the lower-level ADO.NET layer, which the code above does for you automatically.

If you need to configure something at the lower-level ADO.NET layer, use `ConfigureDataSource()` as follows:

```c#
builder.Services.AddDbContextPool<BloggingContext>(opt =>
opt.UseNpgsql(
builder.Configuration.GetConnectionString("BloggingContext"),
o => o.ConfigureDataSource(dataSourceBuilder => dataSourceBuilder.UseClientCertificate(certificate))));
```

`ConfigureDataSource()` provides access to a lower-level [`NpgsqlDataSourceBuilder`](../doc/basic-usage.html#data-source) which you can use to configure all aspects of the Npgsql ADO.NET provider.

> [!WARNING]
> The EF provider internally creates an NpgsqlDataSource and uses that; for most configuration (e.g. connection string), the provider knows to switch between NpgsqlDataSources automatically.
> However, it's not possible to detect configuration differences within the `ConfigureDataSource()`; as a result, avoid performing varying configuration inside `ConfigureDataSource()`, since you may
> get the wrong NpgsqlDataSource. If you find yourself needing to vary Npgsql ADO.NET configuration, create an external NpgsqlDataSource yourself with the desired configuration and pass that to
> `UseNpgsql()` as described below.

### Using an external NpgsqlDataSource

If you're using a version of EF prior to 9.0, the above configuration methods aren't available. You can still create an `NpgsqlDataSource` yourself, and then pass it EF's `UseNpgsql()`:

```c#
var dataSourceBuilder = new NpgsqlDataSourceBuilder(builder.Configuration.GetConnectionString("BloggingContext"));
dataSourceBuilder.MapEnum<Mood>();
dataSourceBuilder.UseNodaTime();
var dataSource = dataSourceBuilder.Build();

builder.Services.AddDbContextPool<BloggingContext>(opt => opt.UseNpgsql(dataSource));
```

## Using an Existing Database (Database-First)
Expand Down
32 changes: 30 additions & 2 deletions conceptual/EFCore.PG/release-notes/9.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,40 @@ Npgsql.EntityFrameworkCore.PostgreSQL version 9.0 is under development; previews
> [!NOTE]
> The following release notes and breaking changes are partial. More will be added nearer to the 9.0 final release.

## Improved configuration for enums and plugins
## Improved, unified configuration experience

The Npgsql EF provider is built on top of the lower-level Npgsql ADO.NET provider; the configuration interface between these two layers was less than ideal, and configuration been more difficult than it should have been. For version 9.0, the configuration experience has been considerably improved.

Since version 7, the Npgsql ADO.NET provider has been moving to [NpgsqlDataSource](../../doc/basic-usage.html#data-source) as the preferred way of configuration connections and obtaining them. At the EF level, it has been possible to pass an NpgsqlDataSource instance to `UseNpgsql()`; but this required that the user separately configure a data source and manage it. In addition, features such as plugins and enums require support from both the EF and ADO.NET layers, forcing users to perform multiple setup actions at the different layers.

With version 9, `UseNpgsql()` becomes a single point for configuration, for both the EF and ADO.NET levels. EF can now internally set up an NpgsqlDataSource, automatically applying all the necessary configuration to it, and also exposes an API to allow users to apply arbitrary configuration to it as well:

```c#
builder.Services.AddDbContextPool<BloggingContext>(opt =>
opt.UseNpgsql(
builder.Configuration.GetConnectionString("BloggingContext"),
o => o
.SetPostgresVersion(13, 0)
.UseNodaTime()
.MapEnum<Mood>("mood")
.ConfigureDataSource(dataSourceBuilder => dataSourceBuilder.UseClientCertificate(certificate))));
```

In the above code, the following configuration gestures are performed:

1. `SetPostgresVersion()` is an EF-only option to produce SQL for PostgreSQL version 13 (avoiding newer incompatible features)
2. `UseNodaTime()`, adds a plugin allowing use of NodaTime for date/time type mapping. This also requires an ADO.NET NodaTime plugin which needed to be configured separately, but this is now done automatically.
3. `MapEnum()` maps a .NET enum type. Like `UseNodaTime()`, this also used to require a separate ADO.NET configuration gesture, but is now done automatically. As an added bonus, doing this now also adds the enum to the model, causing the enum to be created in the database via EF's migrations.
4. `ConfigureDataSource()` exposes an NpgsqlDataSourceBuilder, which you can use to configure arbitrary ADO.NET options. In this example, the certificate is defined for the TLS authentication process.

For more information, see the [getting started docs](../index.html).

### Improved configuration for enums and plugins

Previously, configuration around enums and plugins (NodaTime, NetTopologySuite) was complicated, requiring multiple setup actions at both the EF and the lower-level Npgsql layers. EF 9.0 improves the configuration story, allowing you to configure enums and plugins via a single EF gesture:

```c#
builder.Services.AddDbContext<MyContext>(options => options.UseNpgsql(
builder.Services.AddPooledDbContext<MyContext>(options => options.UseNpgsql(
"<connection string>",
o => o.MapEnum<Mood>("mood")));
```
Expand Down