Skip to content

Commit c12440d

Browse files
authored
Docs on the new enum/plugin config (#352)
Closes #351
1 parent 8ee7cf5 commit c12440d

4 files changed

Lines changed: 131 additions & 59 deletions

File tree

conceptual/EFCore.PG/mapping/enum.md

Lines changed: 50 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,54 +4,74 @@ By default, any enum properties in your model will be mapped to database integer
44

55
However, the Npgsql provider also allows you to map your CLR enums to [database enum types](https://www.postgresql.org/docs/current/static/datatype-enum.html). This option, unique to PostgreSQL, provides the best of both worlds: the enum is internally stored in the database as a number (minimal storage), but is handled like a string (more usable, no need to remember numeric values) and has type safety.
66

7-
## Creating your database enum
7+
## Setting up your enum with EF
88

9-
First, you must specify the PostgreSQL enum type on your model, just like you would with tables, sequences or other databases objects:
9+
> [!NOTE]
10+
> Enum mapping has changed considerably in EF 9.0.
11+
12+
If you're using EF 9.0 or above, simply call `MapEnum` inside your `UseNpgsql` invocation.
13+
14+
### [With a connection string](#tab/with-connection-string)
15+
16+
If you're passing a connection string to `UseNpgsql`, simply add the `MapEnum` call as follows:
1017

1118
```c#
12-
protected override void OnModelCreating(ModelBuilder builder)
13-
=> builder.HasPostgresEnum<Mood>();
19+
builder.Services.AddDbContext<MyContext>(options => options.UseNpgsql(
20+
"<connection string>",
21+
o => o.MapEnum<Mood>("mood")));
1422
```
1523

16-
This causes the EF Core provider to create your enum type, `mood`, with two labels: `happy` and `sad`. This will cause the appropriate migration to be created.
24+
This configures all aspects of Npgsql to use your `Mood` enum - both at the EF and the lower-level Npgsql layer - and ensures that the enum is created in the database in EF migrations.
25+
26+
### [With an external NpgsqlDataSource](#tab/with-datasource)
1727

18-
If you are using `context.Database.Migrate()` to create your enums, you need to instruct Npgsql to reload all types after applying your migrations:
28+
If you're creating an external NpgsqlDataSource and passing it to `UseNpgsql`, you must make sure to map your enum on that data independently of the EF-level setup:
1929

2030
```c#
21-
await context.Database.MigrateAsync(token);
31+
var dataSourceBuilder = new NpgsqlDataSourceBuilder("<connection string>");
32+
dataSourceBuilder.MapEnum<Mood>();
33+
var dataSource = dataSourceBuilder.Build();
2234

23-
if (context.Database.GetDbConnection() is NpgsqlConnection npgsqlConnection)
24-
{
25-
await npgsqlConnection.OpenAsync(token);
26-
try
27-
{
28-
await npgsqlConnection.ReloadTypesAsync();
29-
}
30-
finally
31-
{
32-
await npgsqlConnection.CloseAsync();
33-
}
34-
}
35+
builder.Services.AddDbContext<MyContext>(options => options.UseNpgsql(
36+
dataSource,
37+
o => o.MapEnum<Mood>("mood")));
38+
```
39+
40+
***
41+
42+
### Older EF versions
43+
44+
On versions of EF prior to 9.0, enum setup is more involved and consists of several steps; enum mapping has to be done at the lower-level Npgsql layer, and also requires explicit configuration in the EF model for creation in the database via migrations.
45+
46+
#### Creating your database enum
47+
48+
First, you must specify the PostgreSQL enum type on your model, just like you would with tables, sequences or other databases objects:
49+
50+
```c#
51+
protected override void OnModelCreating(ModelBuilder builder)
52+
=> builder.HasPostgresEnum<Mood>();
3553
```
3654

37-
## Mapping your enum
55+
This causes the EF Core provider to create your enum type, `mood`, with two labels: `happy` and `sad`. This will cause the appropriate migration to be created.
56+
57+
#### Mapping your enum
3858

3959
Even if your database enum is created, Npgsql has to know about it, and especially about your CLR enum type that should be mapped to it:
4060

41-
### [NpgsqlDataSource](#tab/with-datasource)
61+
##### [NpgsqlDataSource](#tab/with-datasource)
4262

4363
Since version 7.0, NpgsqlDataSource is the recommended way to use Npgsql. When using NpgsqlDataSource, map your enum when building your data source:
4464

4565
```c#
46-
// Call UseNodaTime() when building your data source:
66+
// Call MapEnum() when building your data source:
4767
var dataSourceBuilder = new NpgsqlDataSourceBuilder(/* connection string */);
4868
dataSourceBuilder.MapEnum<Mood>();
4969
var dataSource = dataSourceBuilder.Build();
5070

5171
builder.Services.AddDbContext<MyContext>(options => options.UseNpgsql(dataSource));
5272
```
5373

54-
### [Without NpgsqlDatasource](#tab/without-datasource)
74+
##### [Without NpgsqlDatasource](#tab/without-datasource)
5575

5676
Since version 7.0, NpgsqlDataSource is the recommended way to use Npgsql. However, if you're not yet using NpgsqlDataSource, map enums by adding the following code, *before* any EF Core operations take place. An appropriate place for this is in the static constructor on your DbContext class:
5777

@@ -60,18 +80,16 @@ static MyDbContext()
6080
=> NpgsqlConnection.GlobalTypeMapper.MapEnum<Mood>();
6181
```
6282

83+
> [!NOTE]
84+
> If you have multiple context types, all `MapEnum` invocations must be done before *any* of them is used; this means that the code cannot be in your static constructors, but must be moved to the program start.
85+
6386
***
6487

6588
This code lets Npgsql know that your CLR enum type, `Mood`, should be mapped to a database enum called `mood`. Note that if your enum is in a custom schema (not `public`), you must specify that schema in the call to `MapEnum`.
6689

67-
If you're curious as to inner workings, this code maps the enum with the ADO.NET provider - [see here for the full docs](http://www.npgsql.org/doc/types/enums_and_composites.html). When the Npgsql EF Core first initializes, it calls into the ADO.NET provider to get all mapped enums, and sets everything up internally at the EF Core layer as well.
68-
69-
> [!NOTE]
70-
> If you have multiple context types, all `MapEnum` invocations must be done before *any* of them is used; this means that the code cannot be in your static constructors, but must be moved to the program start.
71-
7290
## Using enum properties
7391

74-
Once your enum is mapped and created in the database, you can use your CLR enum type just like any other property:
92+
Once your enum is properly set up with EF, you can use your CLR enum type just like any other property:
7593

7694
```c#
7795
public class Blog
@@ -96,13 +114,13 @@ using (var ctx = new MyDbContext())
96114
The Npgsql provider only allow adding new values to existing enums, and the appropriate migrations will be automatically created as you add values to your CLR enum type. However, PostgreSQL itself doesn't support removing enum values (since these may be in use), and while renaming values is supported, it isn't automatically done by the provider to avoid using unreliable detection heuristics. Renaming an enum value can be done by including [raw SQL](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/managing?tabs=dotnet-core-cli#arbitrary-changes-via-raw-sql) in your migrations as follows:
97115

98116
```c#
99-
migrationBuilder.Sql(@"ALTER TYPE mood RENAME VALUE 'happy' TO 'thrilled';");
117+
migrationBuilder.Sql("ALTER TYPE mood RENAME VALUE 'happy' TO 'thrilled';");
100118
```
101119

102120
As always, test your migrations carefully before running them on production databases.
103121

104122
## Scaffolding from an existing database
105123

106-
If you're creating your model from an existing database, the provider will recognize enums in your database, and scaffold the appropriate `HasPostgresEnum()` lines in your model. However, the scaffolding process has no knowledge of your CLR type, and will therefore skip your enum columns (warnings will be logged). You will have to create the CLR type, add the global mapping and add the properties to your entities.
124+
If you're creating your model from an existing database, the provider will recognize enums in your database, and scaffold the appropriate `HasPostgresEnum()` lines in your model. However, the scaffolding process has no knowledge of your CLR type, and will therefore skip your enum columns (warnings will be logged). You will have to create the CLR type and perform the proper setup as described above.
107125

108-
In the future it may be possible to scaffold the actual enum type (and with it the properties), but this doesn't happen at the moment.
126+
In the future it may be possible to scaffold the actual enum type (and with it the properties), but this isn't supported at the moment.

conceptual/EFCore.PG/mapping/nodatime.md

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,36 +12,45 @@ Beyond NodaTime's general advantages, some specific advantages NodaTime for Post
1212

1313
## Setup
1414

15-
To set up the NodaTime plugin, add the [Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime nuget](https://www.nuget.org/packages/Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime) to your project. Then, configure NodaTime as follows:
15+
To set up the NodaTime plugin, add the [Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime nuget](https://www.nuget.org/packages/Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime) to your project. Then, configure the NodaTime plugin as follows:
1616

17-
### [NpgsqlDataSource](#tab/with-datasource)
17+
### [EF 9.0, with a connection string](#tab/ef9-with-connection-string)
1818

19-
Since version 7.0, NpgsqlDataSource is the recommended way to use Npgsql. When using NpsgqlDataSource, NodaTime currently has to be configured twice - once at the EF level, and once at the underlying ADO.NET level (there are plans to improve this):
19+
If you're passing a connection string to `UseNpgsql`, simply add the `UseNodaTime` call as follows:
2020

2121
```c#
22-
// Call UseNodaTime() when building your data source:
23-
var dataSourceBuilder = new NpgsqlDataSourceBuilder(/* connection string */);
22+
builder.Services.AddDbContext<MyContext>(options => options.UseNpgsql(
23+
"<connection string>",
24+
o => o.UseNodaTime()));
25+
```
26+
27+
This configures all aspects of Npgsql to use the NodaTime plugin - both at the EF and the lower-level Npgsql layer.
28+
29+
### [With an external NpgsqlDataSource](#tab/with-datasource)
30+
31+
If you're creating an external NpgsqlDataSource and passing it to `UseNpgsql`, you must call `UseNodaTime` on your NpgsqlDataSourceBuilder independently of the EF-level setup:
32+
33+
```c#
34+
var dataSourceBuilder = new NpgsqlDataSourceBuilder("<connection string>");
2435
dataSourceBuilder.UseNodaTime();
2536
var dataSource = dataSourceBuilder.Build();
2637

27-
// Then, when configuring EF Core with UseNpgsql(), call UseNodaTime() there as well:
28-
builder.Services.AddDbContext<MyContext>(options =>
29-
options.UseNpgsql(dataSource, o => o.UseNodaTime()));
38+
builder.Services.AddDbContext<MyContext>(options => options.UseNpgsql(
39+
dataSource,
40+
o => o.UseNodaTime()));
3041
```
3142

32-
### [Without NpgsqlDatasource](#tab/without-datasource)
33-
34-
Since version 7.0, NpgsqlDataSource is the recommended way to use Npgsql. However, if you're not yet using NpgsqlDataSource, configure NodaTime as follows:
43+
### [Older EF versions, with a connection string](#tab/legacy-with-connection-string)
3544

3645
```c#
37-
// Configure NodaTime at the ADO.NET level.
46+
// Configure UseNodaTime at the ADO.NET level.
3847
// This code must be placed at the beginning of your application, before any other Npgsql API is called; an appropriate place for this is in the static constructor on your DbContext class:
3948
static MyDbContext()
4049
=> NpgsqlConnection.GlobalTypeMapper.UseNodaTime();
4150

4251
// Then, when configuring EF Core with UseNpgsql(), call UseNodaTime():
4352
builder.Services.AddDbContext<MyContext>(options =>
44-
options.UseNpgsql(/* connection string */, o => o.UseNodaTime()));
53+
options.UseNpgsql("<connection string>", o => o.UseNodaTime()));
4554
```
4655

4756
***

conceptual/EFCore.PG/mapping/nts.md

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,35 @@ Note that the EF Core NetTopologySuite plugin depends on [the Npgsql ADO.NET Net
99

1010
## Setup
1111

12-
To use the NetTopologySuite plugin, add the [Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite nuget](https://www.nuget.org/packages/Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite) to your project. Then, configure NetTopologySuite as followed:
12+
To use the NetTopologySuite plugin, add the [Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite nuget](https://www.nuget.org/packages/Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite) to your project. Then, configure the NetTopologySuite plugin as follows:
1313

14-
### [NpgsqlDataSource](#tab/with-datasource)
14+
### [EF 9.0, with a connection string](#tab/ef9-with-connection-string)
1515

16-
Since version 7.0, NpgsqlDataSource is the recommended way to use Npgsql. When using NpsgqlDataSource, NetTopologySuite currently has to be configured twice - once at the EF level, and once at the underlying ADO.NET level (there are plans to improve this):
16+
If you're passing a connection string to `UseNpgsql`, simply add the `UseNetTopologySuite` call as follows:
1717

1818
```c#
19-
// Call UseNetTopologySuite() when building your data source:
20-
var dataSourceBuilder = new NpgsqlDataSourceBuilder(/* connection string */);
19+
builder.Services.AddDbContext<MyContext>(options => options.UseNpgsql(
20+
"<connection string>",
21+
o => o.UseNetTopologySuite()));
22+
```
23+
24+
This configures all aspects of Npgsql to use the NetTopologySuite plugin - both at the EF and the lower-level Npgsql layer.
25+
26+
### [With an external NpgsqlDataSource](#tab/with-datasource)
27+
28+
If you're creating an external NpgsqlDataSource and passing it to `UseNpgsql`, you must call `UseNetTopologySuite` on your NpgsqlDataSourceBuilder independently of the EF-level setup:
29+
30+
```c#
31+
var dataSourceBuilder = new NpgsqlDataSourceBuilder("<connection string>");
2132
dataSourceBuilder.UseNetTopologySuite();
2233
var dataSource = dataSourceBuilder.Build();
2334

24-
// Then, when configuring EF Core with UseNpgsql(), call UseNetTopologySuite() there as well:
25-
builder.Services.AddDbContext<MyContext>(options =>
26-
options.UseNpgsql(dataSource, o => o.UseNetTopologySuite()));
35+
builder.Services.AddDbContext<MyContext>(options => options.UseNpgsql(
36+
dataSource,
37+
o => o.UseNetTopologySuite()));
2738
```
2839

29-
### [Without NpgsqlDatasource](#tab/without-datasource)
30-
31-
Since version 7.0, NpgsqlDataSource is the recommended way to use Npgsql. However, if you're not yet using NpgsqlDataSource, configure NetTopologySuite as follows:
40+
### [Older EF versions, with a connection string](#tab/legacy-with-connection-string)
3241

3342
```c#
3443
// Configure NetTopologySuite at the ADO.NET level.
@@ -38,12 +47,12 @@ static MyDbContext()
3847

3948
// Then, when configuring EF Core with UseNpgsql(), call UseNetTopologySuite():
4049
builder.Services.AddDbContext<MyContext>(options =>
41-
options.UseNpgsql(/* connection string */, o => o.UseNetTopologySuite()));
50+
options.UseNpgsql("<connection string>", o => o.UseNetTopologySuite()));
4251
```
4352

4453
***
4554

46-
The above sets up all the necessary EF mappings and operation translators. In addition, to make sure that the PostGIS extension is installed in your database, add the following to your DbContext:
55+
The above sets up all the necessary EF mappings and operation translators. If you're using EF 6.0, you also need to make sure that the PostGIS extension is installed in your database (later versions do this automatically). Add the following to your DbContext:
4756

4857
```c#
4958
protected override void OnModelCreating(ModelBuilder builder)

conceptual/EFCore.PG/release-notes/9.0.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,52 @@
22

33
Npgsql.EntityFrameworkCore.PostgreSQL version 9.0 is under development; previews are available on [nuget.org](https://www.nuget.org/packages/Npgsql.EntityFrameworkCore.PostgreSQL).
44

5+
## Improved configuration for enums and plugins
6+
7+
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:
8+
9+
```c#
10+
builder.Services.AddDbContext<MyContext>(options => options.UseNpgsql(
11+
"<connection string>",
12+
o => o.MapEnum<Mood>("mood")));
13+
```
14+
15+
This takes care of everything - EF configuration, lower-level Npgsql configuration and even the addition of the enum to the EF model, which ensures that the enum is created in the database in EF migrations.
16+
17+
See the [enum](../mapping/enum.md), [NodaTime](../mapping/nodatime.md) and [NetTopologySuite](../mapping/nts.md) documentation for more details.
18+
519
## UUIDv7 GUIDs are generated by default
620

721
When your entity types have a `Guid` key, EF Core by default generates key values for new entities client-side - in .NET - before inserting those entity types to the database; this can be better for performance in some situations. Before version 9.0, the provider generated random GUIDs (version 4) by calling the .NET [`Guid.NewGuid()`](https://learn.microsoft.com/en-us/dotnet/api/system.guid.newguid?view=net-8.0#system-guid-newguid) function. Unfortunately, random GUIDs aren't ideal for database indexing and can cause performance issues.
822

9-
Version 9.0 of the provider now generates the recently standardized version 7 GUIDs, which is a sequential GUID type that's more appropriate for database indexes and improves their performance. This new behavior is by default and will take effect simply by upgrading the provider version.
23+
Version 9.0 of the provider now generates the recently standardized version 7 GUIDs, which is a sequential GUID type that's more appropriate for database indexes and improves their performance. This new behavior is on by default and takes effect simply by upgrading the provider version.
1024

1125
See [this post](https://www.cybertec-postgresql.com/en/unexpected-downsides-of-uuid-keys-in-postgresql) for more details and performance numbers on random vs. sequential GUIDs.
1226

1327
Thanks to [@ChrisJollyAU](https://github.com/ChrisJollyAU) and [@Timovzl](https://github.com/Timovzl) for contributing this improvement!
1428

29+
## Breaking changes
30+
31+
### Enum mappings must now be configured at the EF level
32+
33+
Previously, enum configuration involved mapping the enum at the lower-level Npgsql layer (either via `NpgsqlDataSourceBuilder.MapEnum` or via `NpgsqlConnection.GlobalTypeMapper.MapEnum`); the EF provider automatically picked this configuration up for the EF-level setup. Unfortunately, this design created numerous issues and bugs.
34+
35+
As part of the improved enum configuration story in version 9.0 ([see above](#improved-configuration-for-enums-and-plugins)), enums must now be configured at the EF level; although this is a breaking change for existing applications, it usually results in simplified setup code and fixes various bugs and problematic behavior.
36+
37+
If your application calls `UseNpgsql` with a simple connection string (rather than an NpgsqlDataSource), it simply needs to add a `MapEnum` call there:
38+
39+
```c#
40+
builder.Services.AddDbContext<MyContext>(options => options.UseNpgsql(
41+
"<connection string>",
42+
o => o.MapEnum<Mood>("mood")));
43+
```
44+
45+
All other setup code - the `MapEnum` call on `NpgsqlConnection.GlobalTypeMapper` and the `HasPostgresEnum` call in `OnModelCreating` - can be removed.
46+
47+
If your application passes an NpgsqlDataSource to `UseNpgsql`, it also needs to add the `MapEnum` call as above; but the `MapEnum` call on `NpgsqlDataSourceBuilder` must also be kept.
48+
49+
See the [enum documentation](../mapping/enum.md) for more information.
50+
1551
## Contributors
1652

1753
A big thank you to all the following people who contributed to the 9.0 release!

0 commit comments

Comments
 (0)