diff --git a/conceptual/EFCore.PG/mapping/array.md b/conceptual/EFCore.PG/mapping/array.md index 47b11263..15b72215 100644 --- a/conceptual/EFCore.PG/mapping/array.md +++ b/conceptual/EFCore.PG/mapping/array.md @@ -34,6 +34,7 @@ array.Take(2) | [array\[,2\]](https://www.postgr array.Skip(1).Take(2) | [array\[2,3\]](https://www.postgresql.org/docs/current/arrays.html#ARRAYS-ACCESSING) | Added in 8.0 array1 == array2 | [array1 = array2](https://www.postgresql.org/docs/current/static/arrays.html) | array1.SequenceEqual(array2) | [array1 = array2](https://www.postgresql.org/docs/current/static/arrays.html) | +array1.Take(2).SequenceEqual(array2) | [array1\[:2\] = array2](https://www.postgresql.org/docs/current/arrays.html#ARRAYS-ACCESSING) | Added in 11.0; also supports Skip() and Skip().Take() arrayNonColumn.Contains(element) | [element = ANY(arrayNonColumn)](https://www.postgresql.org/docs/current/static/functions-comparisons.html#AEN21104) | Can use regular index arrayColumn.Contains(element) | [arrayColumn @> ARRAY\[element\]](https://www.postgresql.org/docs/current/functions-array.html#ARRAY-OPERATORS-TABLE) | Can use GIN index array.Append(element) | [array_append(array, element)](https://www.postgresql.org/docs/current/functions-array.html#ARRAY-FUNCTIONS-TABLE) | Added in 6.0 diff --git a/conceptual/EFCore.PG/mapping/json.md b/conceptual/EFCore.PG/mapping/json.md index f4ce0edb..09df8eef 100644 --- a/conceptual/EFCore.PG/mapping/json.md +++ b/conceptual/EFCore.PG/mapping/json.md @@ -260,8 +260,8 @@ customer.RootElement.GetProperty("Orders").GetArrayLength() EF.Functions.JsonContains(customer, @"{""Name"": ""Joe"", ""Age"": 25}")1 | [customer @> '{"Name": "Joe", "Age": 25}'](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSONB-OP-TABLE) EF.Functions.JsonContained(@"{""Name"": ""Joe"", ""Age"": 25}", customer)1 | ['{"Name": "Joe", "Age": 25}' <@ customer](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSONB-OP-TABLE) EF.Functions.JsonExists(customer, "Age") | [customer ? 'Age'](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSONB-OP-TABLE) -EF.Functions.JsonExistsAny(customer, "Age", "Address") | [customer ?\| ARRAY\['Age','Address'\]](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSONB-OP-TABLE) -EF.Functions.JsonExistsAll(customer, "Age", "Address") | [customer ?& ARRAY\['Age','Address'\]](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSONB-OP-TABLE) +EF.Functions.JsonExistAny(customer, "Age", "Address") | [customer ?\| ARRAY\['Age','Address'\]](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSONB-OP-TABLE) +EF.Functions.JsonExistAll(customer, "Age", "Address") | [customer ?& ARRAY\['Age','Address'\]](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSONB-OP-TABLE) EF.Functions.JsonTypeof(customer.GetProperty("Age")) == "number" | [jsonb_typeof(customer->'Age') = 'number'](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSON-PROCESSING-TABLE) ### [Legacy POCO Mapping](#tab/poco) @@ -274,8 +274,8 @@ customer.Orders.Length (or Count) EF.Functions.JsonContains(customer, @"{""Name"": ""Joe"", ""Age"": 25}")1 | [customer @> '{"Name": "Joe", "Age": 25}'](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSONB-OP-TABLE) EF.Functions.JsonContained(@"{""Name"": ""Joe"", ""Age"": 25}", e.Customer)1 | ['{"Name": "Joe", "Age": 25}' <@ customer](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSONB-OP-TABLE) EF.Functions.JsonExists(e.Customer, "Age") | [customer ? 'Age'](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSONB-OP-TABLE) -EF.Functions.JsonExistsAny(e.Customer, "Age", "Address") | [customer ?\| ARRAY\['Age','Address'\]](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSONB-OP-TABLE) -EF.Functions.JsonExistsAll(e.Customer, "Age", "Address") | [customer ?& ARRAY\['Age','Address'\]](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSONB-OP-TABLE) +EF.Functions.JsonExistAny(e.Customer, "Age", "Address") | [customer ?\| ARRAY\['Age','Address'\]](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSONB-OP-TABLE) +EF.Functions.JsonExistAll(e.Customer, "Age", "Address") | [customer ?& ARRAY\['Age','Address'\]](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSONB-OP-TABLE) EF.Functions.JsonTypeof(e.Customer.Age) | [jsonb_typeof(customer->'Age')](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSON-PROCESSING-TABLE) *** diff --git a/conceptual/EFCore.PG/mapping/nodatime.md b/conceptual/EFCore.PG/mapping/nodatime.md index ff31884d..a24c1542 100644 --- a/conceptual/EFCore.PG/mapping/nodatime.md +++ b/conceptual/EFCore.PG/mapping/nodatime.md @@ -82,9 +82,13 @@ var oldEvents = context.Events.Where(p => p.SomeDate.Year < 2000); Following is the list of supported NodaTime translations; If an operation you need is missing, please open an issue to request for it. +Starting with version 11.0, `EF.Functions.DateTrunc()` supports `LocalDateTime`, `Instant`, `ZonedDateTime`, `Period` and `Duration`. Its optional time zone argument is only supported for `Instant` and `ZonedDateTime` mapped to `timestamp with time zone`; without that argument, PostgreSQL uses the session time zone for these values. + .NET | SQL | Notes -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ----------- SystemClock.Instance.GetCurrentInstant() | [now()](https://www.postgresql.org/docs/current/functions-datetime.html) | +EF.Functions.DateTrunc("month", value) | [date_trunc('month', value)](https://www.postgresql.org/docs/current/functions-datetime.html) | Added in 11.0 +EF.Functions.DateTrunc("month", instant, "Europe/Berlin") | [date_trunc('month', instant, 'Europe/Berlin')](https://www.postgresql.org/docs/current/functions-datetime.html) | Added in 11.0 LocalDateTime.Date | [date_trunc('day', timestamp)](https://www.postgresql.org/docs/current/functions-datetime.html) | LocalDateTime.Second (also LocalTime, ZonedDateTime) | [date_part('second', timestamp)::INT](https://www.postgresql.org/docs/current/functions-datetime.html) | LocalDateTime.Minute (also LocalTime, ZonedDateTime) | [date_part('minute', timestamp)::INT](https://www.postgresql.org/docs/current/functions-datetime.html) | diff --git a/conceptual/EFCore.PG/mapping/translations.md b/conceptual/EFCore.PG/mapping/translations.md index 4eb9dabb..2d0151be 100644 --- a/conceptual/EFCore.PG/mapping/translations.md +++ b/conceptual/EFCore.PG/mapping/translations.md @@ -18,18 +18,18 @@ string.Concat(str0, str1) | str0 \|\| str1 string.IsNullOrEmpty(value) | value IS NULL OR value = '' string.IsNullOrWhiteSpace(value) | value IS NULL OR btrim(value, E' \t\n\r') = '' stringValue.CompareTo(strB) | CASE WHEN stringValue = strB THEN 0 ... END -stringValue.Contains(value) | stringValue LIKE %value% -stringValue.EndsWith(value) | stringValue LIKE '%' \|\| value +stringValue.Contains(value) | stringValue LIKE %value% | char overload added in 11.0 +stringValue.EndsWith(value) | stringValue LIKE '%' \|\| value | char overload added in 11.0 stringValue.FirstOrDefault() | substr(stringValue, 1, 1) -stringValue.IndexOf(value) | strpos(stringValue, value) - 1 +stringValue.IndexOf(value) | strpos(stringValue, value) - 1 | char overload added in 11.0 stringValue.LastOrDefault() | substr(stringValue, length(stringValue), 1) stringValue.Length | length(stringValue) -stringValue.PadLeft(length) | lpad(stringValue, length) -stringValue.PadLeft(length, char) | lpad(stringValue, length, char) -stringValue.PadRight(length) | rpad(stringValue, length) -stringValue.PadRight(length, char) | rpad(stringValue, length, char) -stringValue.Replace(oldValue, newValue) | replace(stringValue, oldValue, newValue) -stringValue.StartsWith(value) | stringValue LIKE value \|\| '%' +stringValue.PadLeft(length) | CASE WHEN length(stringValue) >= length THEN stringValue ELSE lpad(stringValue, length) END +stringValue.PadLeft(length, char) | CASE WHEN length(stringValue) >= length THEN stringValue ELSE lpad(stringValue, length, char) END +stringValue.PadRight(length) | CASE WHEN length(stringValue) >= length THEN stringValue ELSE rpad(stringValue, length) END +stringValue.PadRight(length, char) | CASE WHEN length(stringValue) >= length THEN stringValue ELSE rpad(stringValue, length, char) END +stringValue.Replace(oldValue, newValue) | replace(stringValue, oldValue, newValue) | char overload added in 11.0 +stringValue.StartsWith(value) | stringValue LIKE value \|\| '%' | char overload added in 11.0 stringValue.Substring(startIndex, length) | substr(stringValue, startIndex + 1, @length) stringValue.ToLower() | lower(stringValue) stringValue.ToUpper() | upper(stringValue) @@ -57,6 +57,8 @@ EF.Functions.StringToArray(s, "\|", "FOO") | [string_agg(s, ' > > See also Npgsql's [NodaTime support](/efcore/mapping/nodatime.html), which is a better and safer way of interacting with date/time data. +Starting with version 11.0, `EF.Functions.DateTrunc()` supports `DateTime`, `DateTimeOffset` and `TimeSpan`. The optional time zone argument is only supported for values mapped to `timestamp with time zone`; when omitted for such values, PostgreSQL uses the session time zone. + .NET | SQL | Notes ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -------- DateTime.UtcNow (6.0+) | [now()](https://www.postgresql.org/docs/current/functions-datetime.html) | @@ -65,6 +67,8 @@ DateTime.Today (6.0+) | [date_trunc( DateTime.UtcNow (legacy) | [now() AT TIME ZONE 'UTC'](https://www.postgresql.org/docs/current/functions-datetime.html) | DateTime.Now (legacy) | [now()](https://www.postgresql.org/docs/current/functions-datetime.html) | DateTime.Today (legacy) | [date_trunc('day', now())](https://www.postgresql.org/docs/current/functions-datetime.html) | +EF.Functions.DateTrunc("month", value) | [date_trunc('month', value)](https://www.postgresql.org/docs/current/functions-datetime.html) | Added in 11.0 +EF.Functions.DateTrunc("month", utcDateTime, "Europe/Berlin") | [date_trunc('month', utcDateTime, 'Europe/Berlin')](https://www.postgresql.org/docs/current/functions-datetime.html) | Added in 11.0; timestamp with time zone only dateTime.AddDays(1) | [dateTime + INTERVAL '1 days'](https://www.postgresql.org/docs/current/functions-datetime.html#OPERATORS-DATETIME-TABLE) | dateTime.AddHours(value) | [dateTime + INTERVAL '1 hours'](https://www.postgresql.org/docs/current/functions-datetime.html#OPERATORS-DATETIME-TABLE) | dateTime.AddMinutes(1) | [dateTime + INTERVAL '1 minutes'](https://www.postgresql.org/docs/current/functions-datetime.html#OPERATORS-DATETIME-TABLE) | @@ -111,16 +115,38 @@ new DateTime(y, m, d, h, m, s, kind) | [make_timest EF.Functions.Sum(timespans) | [sum(timespans)](https://www.postgresql.org/docs/current/functions-aggregate.html#FUNCTIONS-AGGREGATE-TABLE) | See [Aggregate functions](#aggregate-functions). EF.Functions.Average(timespans) | [avg(timespans)](https://www.postgresql.org/docs/current/functions-aggregate.html#FUNCTIONS-AGGREGATE-TABLE) | See [Aggregate functions](#aggregate-functions). +## JSON functions + +See [JSON mapping](json.md) for mapping options and [PostgreSQL's JSON functions and operators](https://www.postgresql.org/docs/current/functions-json.html) for operator semantics. JSON aggregate functions are listed under [Aggregate functions](#aggregate-functions). + +.NET | SQL | Notes +----------------------------------------------- | ------------------------------------ | ----- +EF.Functions.JsonContains(json, contained) | json @> contained | jsonb only +EF.Functions.JsonContained(contained, json) | contained <@ json | jsonb only +EF.Functions.JsonExists(json, key) | json ? key | jsonb only +EF.Functions.JsonExistAny(json, key1, key2) | json ?\| ARRAY[key1, key2] | jsonb only +EF.Functions.JsonExistAll(json, key1, key2) | json ?& ARRAY[key1, key2] | jsonb only +EF.Functions.JsonTypeof(json) | jsonb_typeof(json) or json_typeof(json) | Selected according to the mapped column type +EF.Functions.JsonPathExists(json, "$.Age") | JSON_EXISTS(json, '$.Age') | Added in 11.0; requires PostgreSQL 17 or above + ## Miscellaneous functions -.NET | SQL ----------------------------------------- | -------------------- +.NET | SQL | Notes +---------------------------------------- | -------------------- | ----- collection.Contains(item) | item IN collection enumValue.HasFlag(flag) | enumValue & flag = flag Guid.CreateVersion7() | [uuidv7()](https://www.postgresql.org/docs/current/functions-uuid.html) (added in 10 when targeting PG18, client-evaluated otherwise) Guid.NewGuid() | [gen_random_uuid()](https://www.postgresql.org/docs/current/functions-uuid.html), or [uuid_generate_v4()](https://www.postgresql.org/docs/current/uuid-ossp.html) from [`uuid-ossp`](https://www.postgresql.org/docs/current/uuid-ossp.html) when targeting pre-PG13 nullable.GetValueOrDefault() | coalesce(nullable, 0) nullable.GetValueOrDefault(defaultValue) | coalesce(nullable, defaultValue) +bool.Parse(stringValue) | CAST(stringValue AS boolean) | Added in 11.0 +byte.Parse(stringValue) | CAST(stringValue AS smallint) | Added in 11.0 +short.Parse(stringValue) | CAST(stringValue AS smallint) | Added in 11.0 +int.Parse(stringValue) | CAST(stringValue AS integer) | Added in 11.0 +long.Parse(stringValue) | CAST(stringValue AS bigint) | Added in 11.0 +float.Parse(stringValue) | CAST(stringValue AS real) | Added in 11.0 +double.Parse(stringValue) | CAST(stringValue AS double precision) | Added in 11.0 +decimal.Parse(stringValue) | CAST(stringValue AS numeric) | Added in 11.0 ## Binary functions @@ -128,6 +154,7 @@ nullable.GetValueOrDefault(defaultValue) | coalesce(nullable, defaultValue) ---------------------------- | ------------------------------- | -------- bytes[i] | get_byte(bytes, i) | bytes.Contains(value) | position(value IN bytes) > 0 | +bytes.Any() | length(bytes) > 0 | Added in 11.0 bytes.Length | length(@bytes) | bytes1.SequenceEqual(bytes2) | @bytes = @second | @@ -315,6 +342,7 @@ The PostgreSQL aggregate functions are documented [here](https://www.postgresql. | EF.Functions.Sum(timespans) | sum(timespans) | EF.Functions.Average(timespans) | avg(timespans) | EF.Functions.JsonObjectAgg(tuple_of_2) | json_object_agg(tuple_of_2.first, tuple_of_2.second) +| EF.Functions.JsonbObjectAgg(tuple_of_2) | jsonb_object_agg(tuple_of_2.first, tuple_of_2.second) | ranges.RangeAgg() | range_agg(ranges) | ranges.RangeIntersectAgg() | range_intersect_agg(ranges) | multiranges.RangeIntersectAgg() | range_intersect_agg(multiranges) diff --git a/conceptual/EFCore.PG/misc/temporal-constraints.md b/conceptual/EFCore.PG/misc/temporal-constraints.md index c8172964..950961eb 100644 --- a/conceptual/EFCore.PG/misc/temporal-constraints.md +++ b/conceptual/EFCore.PG/misc/temporal-constraints.md @@ -33,7 +33,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) // Configure the temporal primary key b.HasKey(e => new { e.EmployeeId, e.ValidPeriod }) - .HasWithoutOverlaps(); + .WithoutOverlaps(); }); } ``` @@ -98,7 +98,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasDefaultValueSql("tstzrange(now(), 'infinity', '[)')"); b.HasKey(e => new { e.EmployeeId, e.ValidPeriod }) - .HasWithoutOverlaps(); + .WithoutOverlaps(); }); modelBuilder.Entity(b => @@ -107,7 +107,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .WithMany() .HasForeignKey(e => new { e.EmployeeId, e.AssignmentPeriod }) .HasPrincipalKey(e => new { e.EmployeeId, e.ValidPeriod }) - .HasPeriod(); + .WithPeriod(); }); } ``` @@ -133,6 +133,16 @@ INSERT INTO project_assignments (employee_id, project_name, assignment_period) VALUES (1, 'Legacy Project', tstzrange('2022-01-01', '2022-06-01', '[)')); ``` +## NodaTime ranges + +With the [NodaTime plugin](../mapping/nodatime.md) enabled, temporal constraints also support `Interval` (mapped to `tstzrange`) and `DateInterval` (mapped to `daterange`). For example, the employee's validity period can be declared as: + +```csharp +public Interval ValidPeriod { get; set; } +``` + +Configure the key with `.WithoutOverlaps()` and the foreign key with `.WithPeriod()` as above; both sides of the relationship must use compatible range mappings. The range property must be last in both keys. + ## Querying temporal data When querying temporal data, PostgreSQL's range operators are particularly useful. The containment operator (`@>`) checks if a range contains a specific point in time: diff --git a/conceptual/EFCore.PG/modeling/concurrency.md b/conceptual/EFCore.PG/modeling/concurrency.md index afe8dd9e..7129f07f 100644 --- a/conceptual/EFCore.PG/modeling/concurrency.md +++ b/conceptual/EFCore.PG/modeling/concurrency.md @@ -46,3 +46,9 @@ public class SomeEntity ``` *** + +## Database concurrency failures + +Starting with version 11.0, PostgreSQL serialization failures (`40001`) and deadlocks (`40P01`) during `SaveChanges()` or `SaveChangesAsync()` are reported as `DbUpdateConcurrencyException`, with the original `PostgresException` as the inner exception. This also covers failures during the automatic transaction commit performed by `SaveChanges`. An explicit application call to commit a transaction is not covered by this conversion. + +As with other concurrency failures, the application must decide whether and how to retry the operation. See the [EF Core concurrency documentation](https://learn.microsoft.com/ef/core/saving/concurrency#resolving-concurrency-conflicts) and [execution strategies](../misc/other.md#execution-strategy). diff --git a/conceptual/EFCore.PG/modeling/generated-properties.md b/conceptual/EFCore.PG/modeling/generated-properties.md index f0c43ad8..b480ebae 100644 --- a/conceptual/EFCore.PG/modeling/generated-properties.md +++ b/conceptual/EFCore.PG/modeling/generated-properties.md @@ -27,6 +27,15 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) => modelBuilder.UseIdentityByDefaultColumns(); ``` +Starting with version 11.0, you can disable the default PostgreSQL value generation strategy for the entire model: + +```csharp +protected override void OnModelCreating(ModelBuilder modelBuilder) + => modelBuilder.UseNoIdentityGeneration(); +``` + +Explicitly configured property strategies are unaffected. This does not disable EF value generation or remove database defaults or computed expressions; use `ValueGeneratedNever()` to disable value generation for an individual property. + ### Defining the strategy for a single property Regardless of the model default, you can define a value-generation strategy on a property-by-property basis: diff --git a/conceptual/EFCore.PG/release-notes/11.0.md b/conceptual/EFCore.PG/release-notes/11.0.md index e8321e2e..72a8adb3 100644 --- a/conceptual/EFCore.PG/release-notes/11.0.md +++ b/conceptual/EFCore.PG/release-notes/11.0.md @@ -9,6 +9,16 @@ EF 11 complex types mapped with `ToJson()` now support indexing JSON members thr > [!NOTE] > The provider currently references EF `11.0.0-rc.2.26504.105`, which has two temporary limitations: C# migration scaffolding for JSON member indexes fails because it cannot scaffold a `RelationalJsonIndex` literal, and composite member indexes with mixed ascending/descending directions fail before SQL generation. Uniform ascending or descending composites can generate SQL, but remain affected by the scaffolding limitation. Both issues are fixed in [dotnet/efcore#39089](https://github.com/dotnet/efcore/pull/39089); the provider has not yet consumed those fixes. +## PostgreSQL JSON functions with ToJson + +The existing PostgreSQL JSON functions, such as `EF.Functions.JsonContains()` and `EF.Functions.JsonExists()`, can now be used with complex types and owned entities mapped with `ToJson()`, including nested objects and collections: + +```csharp +var customers = await context.Customers + .Where(c => EF.Functions.JsonContains(c.Details, """{"Name": "Joe"}""")) + .ToListAsync(); +``` + ## Support for PostgreSQL 18 temporal constraints PostgreSQL 18 introduced powerful temporal constraints that allow enforcing data integrity over time periods directly at the database level. The EF Core provider now supports these features, allowing you to define temporal primary keys, unique constraints, and foreign keys. @@ -37,7 +47,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasDefaultValueSql("tstzrange(now(), 'infinity', '[)')"); b.HasKey(e => new { e.EmployeeId, e.ValidPeriod }) - .HasWithoutOverlaps(); + .WithoutOverlaps(); }); } ``` @@ -78,7 +88,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .WithMany() .HasForeignKey(e => new { e.EmployeeId, e.AssignmentPeriod }) .HasPrincipalKey(e => new { e.EmployeeId, e.ValidPeriod }) - .HasPeriod(); + .WithPeriod(); }); } ``` @@ -92,17 +102,46 @@ FOREIGN KEY (employee_id, PERIOD assignment_period) REFERENCES employees (employee_id, PERIOD valid_period); ``` -For more details, see the [temporal constraints documentation](../misc/temporal-constraints.md). +Temporal constraints also support NodaTime `Interval` and `DateInterval` properties when the NodaTime plugin is enabled. For more details, see the [temporal constraints documentation](../misc/temporal-constraints.md). ## Other new features +* Added [`EF.Functions.DateTrunc()`](../mapping/translations.md#date-and-time-functions) for truncating date/time values to units such as hours, days or months, with support for [NodaTime types](../mapping/nodatime.md#operation-translation) and an optional time zone for `timestamp with time zone` values ([#3831](https://github.com/npgsql/efcore.pg/issues/3831)). * Added `EF.Functions.IntersectsBbox(geom1, geom2)` for expressing [the PostGIS `&&` operator](https://postgis.net/docs/geometry_overlaps.html). Thanks to [@bjornharrtell](https://github.com/bjornharrtell) for this contribution! +* Added [`EF.Functions.JsonPathExists()`](../mapping/translations.md#json-functions), translated to `JSON_EXISTS()` on PostgreSQL 17 and above ([#3733](https://github.com/npgsql/efcore.pg/issues/3733)). +* Added [`EF.Functions.FuzzyStringMatchDaitchMokotoff()`](../mapping/translations.md#fuzzy-string-match-functions) for Daitch-Mokotoff phonetic matching with the `fuzzystrmatch` extension on PostgreSQL 16 and above ([#3524](https://github.com/npgsql/efcore.pg/issues/3524)). +* Added [`ModelBuilder.UseNoIdentityGeneration()`](../modeling/generated-properties.md#defining-the-default-strategy-for-the-entire-model) to disable the model's default PostgreSQL identity generation strategy ([#3723](https://github.com/npgsql/efcore.pg/issues/3723)). +* [Covering indexes](../modeling/indexes.md#covering-indexes-include) now support including properties nested within complex types mapped to regular columns. +* Indexes configured with [`IsCreatedConcurrently()`](../modeling/indexes.md#creating-indexes-concurrently) are now also dropped concurrently when removed by migrations ([#3901](https://github.com/npgsql/efcore.pg/issues/3901)). +* Added translation for the [three-argument `TimeZoneInfo.ConvertTimeBySystemTimeZoneId()` overload](../mapping/translations.md#date-and-time-functions), converting between source and destination time zones for `timestamp without time zone` values ([#3938](https://github.com/npgsql/efcore.pg/issues/3938)). +* Added translations for the `char` overloads of `string.IndexOf()`, `Replace()`, `StartsWith()`, `EndsWith()` and `Contains()` ([#3547](https://github.com/npgsql/efcore.pg/issues/3547)). +* Added translations for single-argument `Parse(string)` calls on `bool`, `byte`, `short`, `int`, `long`, `float`, `double` and `decimal`. +* Array `SequenceEqual()` now supports slices produced by `Skip()` and `Take()` ([#3633](https://github.com/npgsql/efcore.pg/issues/3633)). `Intersect().Any()` over static readonly collections with non-null elements can now use PostgreSQL's array overlap operator ([#3638](https://github.com/npgsql/efcore.pg/issues/3638)). +* `Any()` on `byte[]` properties mapped to `bytea` is now translated to a length check. +* [Creating PostgreSQL extensions](../misc/other.md#postgresql-extensions) now uses `CASCADE` to install their dependencies automatically ([#3682](https://github.com/npgsql/efcore.pg/issues/3682)). +* Support specifying the encoding of newly created PostgreSQL databases with `ModelBuilder.UseDatabaseEncoding()` ([#3526](https://github.com/npgsql/efcore.pg/issues/3526)). + +See the [11.0.0 milestone](https://github.com/npgsql/efcore.pg/milestone/75?closed=1) for the full list of issues. ## Breaking changes -* The provider now assumes at least PostgreSQL 16 by default, taking advantage of newer features (previously, PostgreSQL 14 was targeted). You can configure the targeted PostgreSQL version when configuring your context: +### PostgreSQL 16 is targeted by default + +The provider now assumes at least PostgreSQL 16 by default, taking advantage of newer features (previously, PostgreSQL 14 was targeted). You can configure the targeted PostgreSQL version when configuring your context: ```c# protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseNpgsql("", o => o.SetPostgresVersion(14, 0)); ``` + +### Changes to transient and concurrency exceptions + +The default, non-retrying execution strategy no longer wraps transient failures in an additional `InvalidOperationException`. Applications catching that wrapper should instead handle the underlying exception. + +In addition, PostgreSQL serialization failures (`40001`) and deadlocks (`40P01`) during `SaveChanges()` or `SaveChangesAsync()` now throw `DbUpdateConcurrencyException`, with the original `PostgresException` as the inner exception. This includes failures during an automatically committed transaction, but not a transaction committed explicitly by the application. Previously, these failures were generally exposed as `DbUpdateException` wrapped in `InvalidOperationException`. + +These changes do not enable automatic retries. See [execution strategies](../misc/other.md#execution-strategy) and [database concurrency failures](../modeling/concurrency.md#database-concurrency-failures) for details ([#3098](https://github.com/npgsql/efcore.pg/issues/3098)). + +### Initial migration rollback removes model-level objects + +The `Down()` method of newly scaffolded initial migrations now removes model-level enum, range and collation definitions that were previously left behind. Review the generated operations if these objects are shared with other applications ([#3604](https://github.com/npgsql/efcore.pg/issues/3604)).