From e7b15931426751a57f1ff316fc4c9011d434b3df Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Tue, 20 Oct 2026 14:14:01 +0200 Subject: [PATCH 1/2] Document string ToString translation Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b4f6a4da-cf90-40f2-a1e2-2a54035bd9df --- conceptual/EFCore.PG/mapping/translations.md | 1 + 1 file changed, 1 insertion(+) diff --git a/conceptual/EFCore.PG/mapping/translations.md b/conceptual/EFCore.PG/mapping/translations.md index 2d0151be..b84381b4 100644 --- a/conceptual/EFCore.PG/mapping/translations.md +++ b/conceptual/EFCore.PG/mapping/translations.md @@ -32,6 +32,7 @@ stringValue.Replace(oldValue, newValue) | replace(stringVa 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.ToString() | stringValue | Added in 11.0; null values remain null stringValue.ToUpper() | upper(stringValue) stringValue.Trim() | btrim(stringValue) stringValue.Trim(trimChar) | btrim(stringValue, trimChar) From 2e26edc80ba45cf48747cd7b191bd4a7ec38177c Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 24 Oct 2026 23:36:40 +0300 Subject: [PATCH 2/2] Document EFCore.PG 11.0 improvements and translations Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b4f6a4da-cf90-40f2-a1e2-2a54035bd9df --- conceptual/EFCore.PG/mapping/nodatime.md | 1 + conceptual/EFCore.PG/mapping/nts.md | 5 ++ conceptual/EFCore.PG/mapping/translations.md | 32 +++++++++- conceptual/EFCore.PG/release-notes/11.0.md | 67 +++++++++++++++++--- 4 files changed, 94 insertions(+), 11 deletions(-) diff --git a/conceptual/EFCore.PG/mapping/nodatime.md b/conceptual/EFCore.PG/mapping/nodatime.md index a24c1542..d17b8ef4 100644 --- a/conceptual/EFCore.PG/mapping/nodatime.md +++ b/conceptual/EFCore.PG/mapping/nodatime.md @@ -131,6 +131,7 @@ LocalDateTime.InZoneLeniently(DateTimeZoneProviders.Tzdb["Europe/Berlin"]).ToIns ZonedDateTime.ToInstant | No PG operation (.NET-side conversion from ZonedDateTime to Instant only) | Instant.InUtc | No PG operation (.NET-side conversion from Instant to ZonedDateTime only) | Instant.ToDateTimeUtc | No PG operation (.NET-side conversion from Instant to UTC DateTime only) | +LocalDateTime.ToDateTimeUnspecified() | No PG operation (timestamp without time zone is unchanged) | Added in 11.0 EF.Functions.Sum(periods) | [sum(periods)](https://www.postgresql.org/docs/current/functions-aggregate.html#FUNCTIONS-AGGREGATE-TABLE) | See [Aggregate functions](translations.md#aggregate-functions). EF.Functions.Sum(durations) | [sum(durations)](https://www.postgresql.org/docs/current/functions-aggregate.html#FUNCTIONS-AGGREGATE-TABLE) | See [Aggregate functions](translations.md#aggregate-functions). EF.Functions.Average(periods) | [avg(durations)](https://www.postgresql.org/docs/current/functions-aggregate.html#FUNCTIONS-AGGREGATE-TABLE) | See [Aggregate functions](translations.md#aggregate-functions). diff --git a/conceptual/EFCore.PG/mapping/nts.md b/conceptual/EFCore.PG/mapping/nts.md index 3ae651f5..57401136 100644 --- a/conceptual/EFCore.PG/mapping/nts.md +++ b/conceptual/EFCore.PG/mapping/nts.md @@ -162,6 +162,8 @@ EF.Functions.IsWithinDistance(geom1, geom2, d, useSpheriod) | [ST_DWithin(g geom.IsSimple() | [ST_IsSimple(geom)](https://postgis.net/docs/ST_IsSimple.html) geom.IsValid() | [ST_IsValid(geom)](https://postgis.net/docs/ST_IsValid.html) lineString.Length | [ST_Length(lineString)](https://postgis.net/docs/ST_Length.html) +EF.Functions.LineInterpolatePoint(lineString, fraction) | [ST_LineInterpolatePoint(lineString, fraction)](https://postgis.net/docs/ST_LineInterpolatePoint.html) | Added in 11.0 +EF.Functions.LineMerge(geom) | [ST_LineMerge(geom)](https://postgis.net/docs/ST_LineMerge.html) | Added in 11.0 geom.Normalized | [ST_Normalize(geom)](https://postgis.net/docs/ST_Normalize.html) geomCollection.NumGeometries | [ST_NumGeometries(geomCollection)](https://postgis.net/docs/ST_NumGeometries.html) polygon.NumInteriorRings | [ST_NumInteriorRings(polygon)](https://postgis.net/docs/ST_NumInteriorRings.html) @@ -170,6 +172,9 @@ geom1.Overlaps(geom2) | [ST_Overlaps( geom.PointOnSurface | [ST_PointOnSurface(geom)](https://postgis.net/docs/ST_PointOnSurface.html) geom1.Relate(geom2) | [ST_Relate(geom1, geom2)](https://postgis.net/docs/ST_Relate.html) geom.Reverse() | [ST_Reverse(geom)](https://postgis.net/docs/ST_Reverse.html) +EF.Functions.Scale(geom, xFactor, yFactor) | [ST_Scale(geom, xFactor, yFactor)](https://postgis.net/docs/ST_Scale.html) | Added in 11.0 +EF.Functions.Scale(geom, xFactor, yFactor, zFactor) | [ST_Scale(geom, xFactor, yFactor, zFactor)](https://postgis.net/docs/ST_Scale.html) | Added in 11.0 +EF.Functions.SetSrid(geom, srid) | [ST_SetSRID(geom, srid)](https://postgis.net/docs/ST_SetSRID.html) | Added in 11.0 geom1.SRID | [ST_SRID(geom1)](https://postgis.net/docs/ST_SRID.html) lineString.StartPoint | [ST_StartPoint(lineString)](https://postgis.net/docs/ST_StartPoint.html) geom1.SymmetricDifference(geom2) | [ST_SymDifference(geom1, geom2)](https://postgis.net/docs/ST_SymDifference.html) diff --git a/conceptual/EFCore.PG/mapping/translations.md b/conceptual/EFCore.PG/mapping/translations.md index b84381b4..366677d9 100644 --- a/conceptual/EFCore.PG/mapping/translations.md +++ b/conceptual/EFCore.PG/mapping/translations.md @@ -32,7 +32,7 @@ stringValue.Replace(oldValue, newValue) | replace(stringVa 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.ToString() | stringValue | Added in 11.0; null values remain null +stringValue.ToString() | stringValue | Null values remain null stringValue.ToUpper() | upper(stringValue) stringValue.Trim() | btrim(stringValue) stringValue.Trim(trimChar) | btrim(stringValue, trimChar) @@ -72,6 +72,8 @@ EF.Functions.DateTrunc("month", value) | [date_trunc( 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.AddMilliseconds(value) (also DateTimeOffset) | [dateTime + value * INTERVAL '1 milliseconds'](https://www.postgresql.org/docs/current/functions-datetime.html#OPERATORS-DATETIME-TABLE) | Added in 11.0 +dateTime.AddMicroseconds(value) (also DateTimeOffset) | [dateTime + value * INTERVAL '1 microseconds'](https://www.postgresql.org/docs/current/functions-datetime.html#OPERATORS-DATETIME-TABLE) | Added in 11.0 dateTime.AddMinutes(1) | [dateTime + INTERVAL '1 minutes'](https://www.postgresql.org/docs/current/functions-datetime.html#OPERATORS-DATETIME-TABLE) | dateTime.AddMonths(1) | [dateTime + INTERVAL '1 months'](https://www.postgresql.org/docs/current/functions-datetime.html#OPERATORS-DATETIME-TABLE) | dateTime.AddSeconds(1) | [dateTime + INTERVAL '1 seconds'](https://www.postgresql.org/docs/current/functions-datetime.html#OPERATORS-DATETIME-TABLE) | @@ -81,6 +83,8 @@ dateTime.Day | [date_part(' dateTime.DayOfWeek | [floor(date_part('dow', dateTime))::INT](https://www.postgresql.org/docs/current/functions-datetime.html) | dateTime.DayOfYear | [date_part('doy', dateTime)::INT](https://www.postgresql.org/docs/current/functions-datetime.html) | dateTime.Hour | [date_part('hour', dateTime)::INT](https://www.postgresql.org/docs/current/functions-datetime.html) | +dateTime.Millisecond (also DateTimeOffset, TimeOnly) | [floor(date_part('milliseconds', dateTime))::INT % 1000](https://www.postgresql.org/docs/current/functions-datetime.html) | Added in 11.0 +dateTime.Microsecond (also DateTimeOffset, TimeOnly) | [date_part('microseconds', dateTime)::INT % 1000](https://www.postgresql.org/docs/current/functions-datetime.html) | Added in 11.0 dateTime.Minute | [date_part('minute', dateTime)::INT](https://www.postgresql.org/docs/current/functions-datetime.html) | dateTime.Month | [date_part('month', dateTime)::INT](https://www.postgresql.org/docs/current/functions-datetime.html) | dateTime.Second | [date_part('second', dateTime)::INT](https://www.postgresql.org/docs/current/functions-datetime.html) | @@ -130,6 +134,29 @@ EF.Functions.JsonExistAll(json, key1, key2) | json ?& ARRAY[key1, key2] 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 +## Hstore translations + +The following operations are supported for `Dictionary` and `ImmutableDictionary` mapped to PostgreSQL [`hstore`](https://www.postgresql.org/docs/current/hstore.html). Operations shown with `immutableDictionary` apply only to `ImmutableDictionary`. + +| .NET | SQL | Notes | +| ---- | --- | ----- | +| dictionary[key] | dictionary -> key | Added in 11.0 | +| dictionary.GetValueOrDefault(key) | dictionary -> key | Added in 11.0 | +| dictionary.GetValueOrDefault(key, fallback) | CASE WHEN dictionary ? key THEN dictionary -> key ELSE fallback END | Added in 11.0 | +| dictionary.ContainsKey(key) | dictionary ? key | Added in 11.0 | +| dictionary.ContainsValue(value) | array_position(avals(dictionary), value) IS NOT NULL | Added in 11.0 | +| dictionary.Count | cardinality(akeys(dictionary)) | Added in 11.0 | +| immutableDictionary.IsEmpty | cardinality(akeys(immutableDictionary)) = 0 | Added in 11.0 | +| immutableDictionary.Remove(key) | immutableDictionary - key | Added in 11.0 | +| immutableDictionary.RemoveRange(keys) | immutableDictionary - keys | Added in 11.0 | +| immutableDictionary.SetItems(otherDictionary) | immutableDictionary \|\| otherDictionary | Added in 11.0 | +| keys.All(key => dictionary.ContainsKey(key)) | dictionary ?& keys | Added in 11.0 | +| keys.Any(key => dictionary.ContainsKey(key)) | dictionary ?\| keys | Added in 11.0 | + +Lookups return SQL `NULL` for missing keys, stored NULL values and NULL hstores, rather than throwing for a missing key. The explicit `GetValueOrDefault` fallback distinguishes a missing key from a key whose stored value is NULL. Key existence and counts include stored-NULL entries, and `ContainsValue(null)` finds stored NULL values. + +`SetItems` accepts another hstore-mapped mutable or immutable dictionary, not arbitrary sequences of key/value pairs. + ## Miscellaneous functions .NET | SQL | Notes @@ -156,8 +183,11 @@ decimal.Parse(stringValue) | CAST(stringValue AS numeric) | Added bytes[i] | get_byte(bytes, i) | bytes.Contains(value) | position(value IN bytes) > 0 | bytes.Any() | length(bytes) > 0 | Added in 11.0 +bytes.Concat(bytes2) | bytes \|\| bytes2 | Added in 11.0 bytes.Length | length(@bytes) | bytes1.SequenceEqual(bytes2) | @bytes = @second | +bitArray.Get(index) | get_bit(bitArray, index) = 1 | Added in 11.0 +bitArray[index] | get_bit(bitArray, index) = 1 | Added in 11.0 ## Math functions diff --git a/conceptual/EFCore.PG/release-notes/11.0.md b/conceptual/EFCore.PG/release-notes/11.0.md index 72a8adb3..60f97b39 100644 --- a/conceptual/EFCore.PG/release-notes/11.0.md +++ b/conceptual/EFCore.PG/release-notes/11.0.md @@ -104,22 +104,65 @@ REFERENCES employees (employee_id, PERIOD valid_period); 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). +## New translations + +EFCore.PG 11 brings many new query translations across the board: + +| .NET | SQL | Notes | +| ---- | --- | ----- | +| [EF.Functions.DateTrunc("month", value)](../mapping/translations.md#date-and-time-functions) | date_trunc('month', value) | Also supports [NodaTime types](../mapping/nodatime.md#operation-translation) and an optional time zone for timestamptz ([#3831](https://github.com/npgsql/efcore.pg/issues/3831)). | +| [EF.Functions.JsonPathExists(json, "$.Age")](../mapping/translations.md#json-functions) | JSON_EXISTS(json, '$.Age') | PostgreSQL 17+ ([#3733](https://github.com/npgsql/efcore.pg/issues/3733)). | +| [EF.Functions.FuzzyStringMatchDaitchMokotoff(text)](../mapping/translations.md#fuzzy-string-match-functions) | daitch_mokotoff(text) | Requires fuzzystrmatch on PostgreSQL 16+ ([#3524](https://github.com/npgsql/efcore.pg/issues/3524)). | +| [TimeZoneInfo.ConvertTimeBySystemTimeZoneId(dateTime, sourceTimeZoneId, destinationTimeZoneId)](../mapping/translations.md#date-and-time-functions) | (dateTime AT TIME ZONE sourceTimeZoneId) AT TIME ZONE destinationTimeZoneId | timestamp without time zone ([#3938](https://github.com/npgsql/efcore.pg/issues/3938)). | +| [dateTime.Millisecond](../mapping/translations.md#date-and-time-functions) | floor(date_part('milliseconds', dateTime))::INT % 1000 | Also DateTimeOffset, TimeOnly ([#3406](https://github.com/npgsql/efcore.pg/issues/3406)). | +| [dateTime.Microsecond](../mapping/translations.md#date-and-time-functions) | date_part('microseconds', dateTime)::INT % 1000 | Also DateTimeOffset, TimeOnly ([#3406](https://github.com/npgsql/efcore.pg/issues/3406)). | +| [dateTime.AddMilliseconds(value)](../mapping/translations.md#date-and-time-functions) | dateTime + value * INTERVAL '1 milliseconds' | Also DateTimeOffset ([#3449](https://github.com/npgsql/efcore.pg/issues/3449)). | +| [dateTime.AddMicroseconds(value)](../mapping/translations.md#date-and-time-functions) | dateTime + value * INTERVAL '1 microseconds' | Also DateTimeOffset ([#3449](https://github.com/npgsql/efcore.pg/issues/3449)). | +| [LocalDateTime.ToDateTimeUnspecified()](../mapping/nodatime.md#operation-translation) | No PG operation | NodaTime ([#4048](https://github.com/npgsql/efcore.pg/issues/4048)). | +| stringValue.IndexOf(value) | strpos(stringValue, value) - 1 | char overload ([#3547](https://github.com/npgsql/efcore.pg/issues/3547)). | +| stringValue.Replace(oldValue, newValue) | replace(stringValue, oldValue, newValue) | char overload | +| stringValue.StartsWith(value) | stringValue LIKE value \|\| '%' | char overload | +| stringValue.EndsWith(value) | stringValue LIKE '%' \|\| value | char overload | +| stringValue.Contains(value) | stringValue LIKE %value% | char overload | +| int.Parse(stringValue) | CAST(stringValue AS integer) | Also bool, byte, short, long, float, double and decimal | +| [array1.Take(2).SequenceEqual(array2)](../mapping/array.md#operation-translation) | array1\[:2\] = array2 | Also Skip() and Skip().Take() ([#3633](https://github.com/npgsql/efcore.pg/issues/3633)). | +| [array1.Intersect(array2).Any()](../mapping/array.md#operation-translation) | array1 && array2 | Now supports static readonly collections with non-null elements ([#3638](https://github.com/npgsql/efcore.pg/issues/3638)). | +| [bytes.Any()](../mapping/translations.md#binary-functions) | length(bytes) > 0 | byte[] mapped to bytea | +| [bytes.Concat(bytes2)](../mapping/translations.md#binary-functions) | bytes \|\| bytes2 | byte[] columns mapped to bytea ([#2200](https://github.com/npgsql/efcore.pg/pull/2200)). | +| [bitArray.Get(index)](../mapping/translations.md#binary-functions) | get_bit(bitArray, index) = 1 | Also bitArray[index] ([#2836](https://github.com/npgsql/efcore.pg/issues/2836)). | +| [EF.Functions.IntersectsBbox(geom1, geom2)](../mapping/nts.md#operation-translation) | [geom1 && geom2](https://postgis.net/docs/geometry_overlaps.html) | Thanks to [@bjornharrtell](https://github.com/bjornharrtell)! | +| [EF.Functions.LineInterpolatePoint(lineString, fraction)](../mapping/nts.md#operation-translation) | [ST_LineInterpolatePoint(lineString, fraction)](https://postgis.net/docs/ST_LineInterpolatePoint.html) | [#2632](https://github.com/npgsql/efcore.pg/issues/2632) | +| [EF.Functions.LineMerge(geom)](../mapping/nts.md#operation-translation) | [ST_LineMerge(geom)](https://postgis.net/docs/ST_LineMerge.html) | [#2632](https://github.com/npgsql/efcore.pg/issues/2632) | +| [EF.Functions.Scale(geom, xFactor, yFactor)](../mapping/nts.md#operation-translation) | [ST_Scale(geom, xFactor, yFactor)](https://postgis.net/docs/ST_Scale.html) | Also supports a zFactor; scales relative to the coordinate origin ([#829](https://github.com/npgsql/efcore.pg/issues/829)). | +| [EF.Functions.SetSrid(geom, srid)](../mapping/nts.md#operation-translation) | [ST_SetSRID(geom, srid)](https://postgis.net/docs/ST_SetSRID.html) | Sets SRID metadata without changing coordinates; use Transform for reprojection ([#3245](https://github.com/npgsql/efcore.pg/issues/3245)). | + +The following [hstore translations](../mapping/translations.md#hstore-translations) are now supported for `Dictionary` and `ImmutableDictionary`, with additional operations specific to immutable dictionaries ([#4082](https://github.com/npgsql/efcore.pg/pull/4082)): + +| .NET | SQL | Notes | +| ---- | --- | ----- | +| dictionary[key] | dictionary -> key | Added in 11.0 | +| dictionary.GetValueOrDefault(key) | dictionary -> key | Added in 11.0 | +| dictionary.GetValueOrDefault(key, fallback) | CASE WHEN dictionary ? key THEN dictionary -> key ELSE fallback END | Added in 11.0 | +| dictionary.ContainsKey(key) | dictionary ? key | Added in 11.0 | +| dictionary.ContainsValue(value) | array_position(avals(dictionary), value) IS NOT NULL | Added in 11.0 | +| dictionary.Count | cardinality(akeys(dictionary)) | Added in 11.0 | +| immutableDictionary.IsEmpty | cardinality(akeys(immutableDictionary)) = 0 | Added in 11.0 | +| immutableDictionary.Remove(key) | immutableDictionary - key | Added in 11.0 | +| immutableDictionary.RemoveRange(keys) | immutableDictionary - keys | Added in 11.0 | +| immutableDictionary.SetItems(otherDictionary) | immutableDictionary \|\| otherDictionary | Added in 11.0 | +| keys.All(key => dictionary.ContainsKey(key)) | dictionary ?& keys | Added in 11.0 | +| keys.Any(key => dictionary.ContainsKey(key)) | dictionary ?\| keys | Added in 11.0 | + ## 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)). +* [Generated `tsvector` columns and full-text expression indexes](../mapping/full-text-search.md#setting-up-and-querying-a-full-text-search-index-on-an-entity) now support integer and exact-numeric inputs by casting them to text ([#2615](https://github.com/npgsql/efcore.pg/issues/2615)). +* [`MapEnum`](../mapping/enum.md) now respects type-level `[PgName]` attributes when no PostgreSQL enum name is supplied ([#3379](https://github.com/npgsql/efcore.pg/issues/3379)). * 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)). +* Custom collation references now respect the model's default schema and correctly handle dots in collation and schema names across migrations, queries and scaffolding ([#4072](https://github.com/npgsql/efcore.pg/pull/4072)). See the [11.0.0 milestone](https://github.com/npgsql/efcore.pg/milestone/75?closed=1) for the full list of issues. @@ -144,4 +187,8 @@ These changes do not enable automatic retries. See [execution strategies](../mis ### 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)). +Rolling back a newly scaffolded initial migration now drops enum, range and collation definitions that were previously left behind. If other applications use these objects, the rollback can fail due to their dependencies; remove the corresponding drop operations from `Down()` if the objects must be preserved ([#3604](https://github.com/npgsql/efcore.pg/issues/3604)). + +### Collation annotations containing literal double quotes + +Previously scaffolded snapshots and migrations with raw collation annotation keys containing literal double quotes in collation or schema names can be misinterpreted by the new identifier encoding. They may fail to decode or reference the wrong collation; update affected annotation keys manually before using them with 11.0 ([#4072](https://github.com/npgsql/efcore.pg/pull/4072)).