Skip to content

Commit 53262dd

Browse files
committed
Release notes and tweaks around EFCore.PG support for JSON
1 parent d3a0216 commit 53262dd

3 files changed

Lines changed: 137 additions & 7 deletions

File tree

‎conceptual/EFCore.PG/mapping/json.md‎

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@ With string mapping, the EF Core provider will save and load properties to datab
5757

5858
If your column JSON contains documents with a stable schema, you can map them to your own .NET types (or POCOs); EF will use System.Text.Json APIs under the hood to serialize instances of your types to JSON documents before sending them to the database, and to deserialize documents coming back from the database. This effectively allows mapping an arbitrary .NET type - or object graph - to a single column in the database.
5959

60-
EF 7.0 introduced the "JSON Columns" feature, which maps a database JSON column via EF's "owned entity" mapping concept, using `ToJson()`. In this approach, EF fully models the types within the JSON document - just like it models regular tables and columns - and uses that information to perform better queries and updates. Full support for ToJson has been added to version 8.0 of the Npgsql EF provider.
60+
As of EF 10, the recommended way to map .NET types to JSON in the database is via complex types ([see EF release notes](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-10.0/whatsnew#json)). In this mode, EF is fully aware of the structure of your JSON document - just like it's aware of your tables and columns - and provides powerful, rich querying and updating capabilities. Prior to EF 10, similar modeling was available via the "owned entity" concept, but this modeling created several issues ([see here for more details](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-10.0/whatsnew#json)). If you're using EF 10 or above, complex types are the recommended way to map .NET types.
6161

62-
As an alternative, prior to version 8.0, the Npgsql EF provider has supported JSON POCO mapping by simply delegating serialization/deserialization to System.Text.Json; in this model, EF itself model the contents of the JSON document, and cannot take that structure into account for queries and updates. This approach can now be considered deprecated as it allows for less powerful mapping and supports less query types; using ToJson() is now the recommended way to map POCOs to JSON.
62+
As an 3rd alternative, prior to version 8.0, the Npgsql EF provider has supported JSON POCO mapping by simply delegating serialization/deserialization to System.Text.Json; in this mode, EF itself is oblivious to the contents of the JSON document, and cannot take that structure into account for queries and updates. This approach can now be considered deprecated as it allows for less powerful mapping and supports less query types; using complex types with `ToJson()` is now the recommended way to map POCOs to JSON.
6363

64-
### ToJson (owned entity mapping)
64+
### EF modeling with ToJson (recommended)
6565

66-
Npgsql's support for `ToJson()` is fully aligned with the general EF support; see the [EF documentation for more information](https://learn.microsoft.com/ef/core/what-is-new/ef-core-7.0/whatsnew#json-columns).
66+
Npgsql's support for `ToJson()` is fully aligned with the general EF support; see the [EF documentation for more information](https://learn.microsoft.com/ef/core/what-is-new/ef-core-10.0/whatsnew#json).
6767

6868
To get you started quickly, assume that we have the following Customer type, with a Details property that we want to map to a single JSON column in the database:
6969

@@ -90,6 +90,18 @@ public class Order // Part of the JSON column
9090

9191
To instruct EF to map CustomerDetails - and within it, Order - to a JSON column, configure it as follows:
9292

93+
#### [Complex types (EF10+)](#tab/complex-types)
94+
95+
```csharp
96+
protected override void OnModelCreating(ModelBuilder modelBuilder)
97+
{
98+
modelBuilder.Entity<Customer>()
99+
.ComplexProperty(c => c.Details, d => d.ToJson());
100+
}
101+
```
102+
103+
#### [Owned entities](#tab/owned entities)
104+
93105
```csharp
94106
protected override void OnModelCreating(ModelBuilder modelBuilder)
95107
{
@@ -102,6 +114,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
102114
}
103115
```
104116

117+
***
118+
105119
At this point you can interact with the Customer just like you would normally, and EF will seamlessly serialize and deserialize it to a JSON column in the database. You can also perform LINQ queries which reference properties inside the JSON document, and these will get translated to SQL.
106120

107121
### Legacy POCO mapping (deprecated)
@@ -134,7 +148,7 @@ public class Order // Part of the JSON column
134148
}
135149
```
136150

137-
### [Fluent API](#tab/fluent-api)
151+
#### [Fluent API](#tab/fluent-api)
138152

139153
```csharp
140154
class MyContext : DbContext

‎conceptual/EFCore.PG/release-notes/10.0.md‎

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,119 @@
22

33
Npgsql.EntityFrameworkCore.PostgreSQL version 10.0 is now in development, preview versions are available on [nuget.org](https://www.nuget.org/packages/Npgsql.EntityFrameworkCore.PostgreSQL).
44

5+
## Full support for EF 10 JSON complex types
6+
7+
EF 10 introduced support for mapping .NET types as JSON complex types, resolving several issues that existed with the previous JSON mapping via owned entities ([see release notes for more information](https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-10.0/whatsnew#json)). The PG provider providers full support for this feature full support for this as well:
8+
9+
```c#
10+
modelBuilder.Entity<Customer>(b =>
11+
{
12+
b.ComplexProperty(c => c.ShippingAddress, c => c.ToJson());
13+
b.ComplexProperty(c => c.BillingAddress, c => c.ToJson());
14+
});
15+
```
16+
17+
This configuration causes the following table to be created for your customers:
18+
19+
```sql
20+
CREATE TABLE "Customers" (
21+
"Id" integer GENERATED BY DEFAULT AS IDENTITY,
22+
"Name" text,
23+
"BillingAddress" jsonb NOT NULL,
24+
"ShippingAddress" jsonb NOT NULL,
25+
CONSTRAINT "PK_Customers" PRIMARY KEY ("Id")
26+
);
27+
```
28+
29+
This is now the preferred way to perform strongly-typed JSON mapping of arbitrary .NET types, and replaces owned entities and [legacy POCO mapping](../mapping/json.md?#legacy-poco-mapping-deprecated).
30+
31+
The provider now also supports performing partial updates within JSON documents using `ExecuteUpdate`. For example, the following efficiently copies overwrites all Customers' shipping address streets with their billing address streets:
32+
33+
```c#
34+
await context.Customers.ExecuteUpdateAsync(s =>
35+
s.SetProperty(b => b.ShippingAddress.Street, b => b.BillingAddress.Street));
36+
```
37+
38+
This produces the following SQL:
39+
40+
```sql
41+
UPDATE "Customers" AS c
42+
SET "ShippingAddress" = jsonb_set(c."ShippingAddress", '{Street}', c."BillingAddress" -> 'Street')
43+
```
44+
45+
## Better support for JSON scalar (primitive) collections
46+
47+
In most relational databases, scalar collections are mapped to a JSON column, the the collection is serialized to a JSON array in the database. PostgreSQL, however, is unique in providing a 1st-class array type, so the EF provider maps scalar collections to array instead. For example, given the following type:
48+
49+
```c#
50+
public class Customer
51+
{
52+
public int Id { get; set; }
53+
public string[] Tags { get; set; }
54+
}
55+
```
56+
57+
... the PostgreSQL provider will create the following table (note that `text[]` array column):
58+
59+
```sql
60+
CREATE TABLE "Customers" (
61+
"Id" integer GENERATED BY DEFAULT AS IDENTITY,
62+
"Tags" text[] NOT NULL,
63+
CONSTRAINT "PK_Customers" PRIMARY KEY ("Id")
64+
);
65+
```
66+
67+
However, when scalar collections are nested within a JSON document, they must be mapped to JSON arrays, as in other databases:
68+
69+
```c#
70+
public class Customer
71+
{
72+
public int Id { get; set; }
73+
public Address Address { get; set; }
74+
}
75+
76+
public class Address
77+
{
78+
// ...
79+
80+
public string[] Tags { get; set; }
81+
}
82+
```
83+
84+
Version 10 of the provider now produces much better SQL when querying such nested scalar collections. For example, when querying using Contains:
85+
86+
```c#
87+
var customers = await context.Customers.Where(b => b.ShippingAddress.Tags.Contains("foo")).ToListAsync();
88+
```
89+
90+
... previous versions of the provider generated the following complicated (and inefficient) SQL:
91+
92+
```sql
93+
SELECT c."Id", c."Name", c."ShippingAddress"
94+
FROM "Customers" AS c
95+
WHERE 'foo' = ANY ((ARRAY(SELECT CAST(element AS text) FROM jsonb_array_elements_text(c."ShippingAddress" -> 'Tags') WITH ORDINALITY AS t(element) ORDER BY ordinality)))
96+
```
97+
98+
Version 10, in contrast, produces the following cleaner SQL, which can also benefit from indexes:
99+
100+
```sql
101+
SELECT c."Id", c."Name", c."ShippingAddress"
102+
FROM "Customers" AS c
103+
WHERE (c."ShippingAddress" -> 'Tags') @> to_jsonb('foo'::text)
104+
```
105+
106+
Finally, version 10 of the provider also allows you to map a non-nested scalar collection to a JSON column, instead of to an array column, and provides fully querying capabilities:
107+
108+
```c#
109+
public class Customer
110+
{
111+
// ...
112+
113+
[Column(TypeName = "jsonb")]
114+
public string[] Tags { get; set; }
115+
}
116+
```
117+
5118
## Support for PostgreSQL 8 virtual generated columns
6119

7120
Before PostgreSQL 18, generated (or "computed") columns could only be stored, meaning they were computed when a row is inserted or updated, and take up space on disk just like regular columns. PostgreSQL 18 introduced support for *virtual* generated columns, which are instead calculated when read, and take up no space on disk. Virtual columns can be defined with version 10 of the PostgreSQL provider as follows:
@@ -19,9 +132,12 @@ Note that previously, `stored: true` had to be specified in the above code sampl
19132

20133
For more information, [see the documentation](../modeling/generated-properties.md#computed-generated-columns).
21134

135+
## Support for UUIDv7
136+
137+
By default, EF generates GUID (or UUID) values locally in .NET, rather than relying on the database to generate them. Version 9 of the PG provider already switched to generating UUIDv7 values by default ([see release note](9.0.md#uuidv7-guids-are-generated-by-default)), which are significantly better for database indexes. PostgreSQL 18 also added the [`uuidv7()`](https://www.postgresql.org/docs/18/functions-uuid.html#FUNC_UUID_GEN_TABLE) built-in function, which allows database generation of UUIDv7 values. In EFCore.PG 10, if you configure the provider to target PG 18 (`.UseNpgsql("...", o => o.SetPostgresVersion(18, 0))`), the provider will also translate [`Guid.CreateVersion7()`](https://learn.microsoft.com/dotnet/api/system.guid.createversion7) to that function.
138+
22139
## Other new features
23140

24-
* When the target PostgreSQL is version is set to 18 (`.UseNpgsql("...", o => o.SetPostgresVersion(18, 0))`), translate [`Guid.CreateVersion7()`](https://learn.microsoft.com/dotnet/api/system.guid.createversion7) to the new [`uuidv7()`](https://www.postgresql.org/docs/18/functions-uuid.html) function.
25141
* NodaTime `LocalDate.At()` and `LocalDate.AtMidnight()` are now translated.
26142

27143
See the [10.0.0 milestone](https://github.com/npgsql/efcore.pg/milestone/68?closed=1) for the full list of Npgsql EF provider issues.

‎conceptual/EFCore.PG/toc.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
href: index.md
33
- name: Release notes
44
items:
5-
- name: "10.0 (preview)"
5+
- name: "10.0 (rc)"
66
href: release-notes/10.0.md
77
- name: "9.0"
88
href: release-notes/9.0.md

0 commit comments

Comments
 (0)