From d08399a34308a3ae645530ad165457edbfa68da0 Mon Sep 17 00:00:00 2001 From: Ahmed Date: Wed, 4 Nov 2026 20:31:50 +0000 Subject: [PATCH] Preserve the M ordinate in geometry SQL literals and JSON Both the SQL literal generated by NpgsqlGeometryTypeMapping and the WKT written by NpgsqlJsonGeometryWktReaderWriter used the default NTS WKT output, which drops M. Write with WKTWriter(4) so XYM and XYZM geometries keep their M value; XY and XYZ output is unchanged. Fixes #4093 --- .../Internal/NpgsqlGeometryTypeMapping.cs | 2 +- .../NpgsqlJsonGeometryWktReaderWriter.cs | 2 +- .../JsonTypesNpgsqlTest.cs | 4 +-- .../NpgsqlNetTopologySuiteTypeMappingTest.cs | 32 +++++++++++++++++++ 4 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 test/EFCore.PG.Tests/Storage/NpgsqlNetTopologySuiteTypeMappingTest.cs diff --git a/src/EFCore.PG.NTS/Storage/Internal/NpgsqlGeometryTypeMapping.cs b/src/EFCore.PG.NTS/Storage/Internal/NpgsqlGeometryTypeMapping.cs index 8a9fa940dd..86a33b64c4 100644 --- a/src/EFCore.PG.NTS/Storage/Internal/NpgsqlGeometryTypeMapping.cs +++ b/src/EFCore.PG.NTS/Storage/Internal/NpgsqlGeometryTypeMapping.cs @@ -91,7 +91,7 @@ protected override string GenerateNonNullSqlLiteral(object value) } builder - .Append(geometry.AsText()) + .Append(new WKTWriter(4).Write(geometry)) .Append('\''); return builder.ToString(); diff --git a/src/EFCore.PG.NTS/Storage/Internal/NpgsqlJsonGeometryWktReaderWriter.cs b/src/EFCore.PG.NTS/Storage/Internal/NpgsqlJsonGeometryWktReaderWriter.cs index 4685aa0cec..bd51ab6ba9 100644 --- a/src/EFCore.PG.NTS/Storage/Internal/NpgsqlJsonGeometryWktReaderWriter.cs +++ b/src/EFCore.PG.NTS/Storage/Internal/NpgsqlJsonGeometryWktReaderWriter.cs @@ -30,7 +30,7 @@ public override Geometry FromJsonTyped(ref Utf8JsonReaderManager manager, object /// public override void ToJsonTyped(Utf8JsonWriter writer, Geometry value) { - var wkt = value.ToText(); + var wkt = new WKTWriter(4).Write(value); // If the SRID is defined, prefix the WKT with it (SRID=4326;POINT(-44.3 60.1)) // Although this is a PostgreSQL extension, NetTopologySuite supports it (see #3236) diff --git a/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs index 7ad9020e17..83cfece046 100644 --- a/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/JsonTypesNpgsqlTest.cs @@ -488,7 +488,7 @@ public override async Task Can_read_write_point_with_M() await Can_read_and_write_JSON_value( nameof(PointMType.PointM), factory.CreatePoint(new CoordinateM(2, 4, 6)), - """{"Prop":"SRID=4326;POINT (2 4)"}"""); + """{"Prop":"SRID=4326;POINT M(2 4 6)"}"""); } public override async Task Can_read_write_point_with_Z() @@ -508,7 +508,7 @@ public override async Task Can_read_write_point_with_Z_and_M() await Can_read_and_write_JSON_value( nameof(PointZMType.PointZM), factory.CreatePoint(new CoordinateZM(1, 2, 3, 4)), - """{"Prop":"SRID=4326;POINT Z(1 2 3)"}"""); + """{"Prop":"SRID=4326;POINT ZM(1 2 3 4)"}"""); } public override async Task Can_read_write_line_string() diff --git a/test/EFCore.PG.Tests/Storage/NpgsqlNetTopologySuiteTypeMappingTest.cs b/test/EFCore.PG.Tests/Storage/NpgsqlNetTopologySuiteTypeMappingTest.cs new file mode 100644 index 0000000000..07f67fd1c6 --- /dev/null +++ b/test/EFCore.PG.Tests/Storage/NpgsqlNetTopologySuiteTypeMappingTest.cs @@ -0,0 +1,32 @@ +using NetTopologySuite.Geometries; +using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal; + +namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage; + +public class NpgsqlNetTopologySuiteTypeMappingTest +{ + [Fact] + public void GenerateSqlLiteral_returns_point_literal() + => Assert.Equal( + "GEOMETRY 'POINT (1 2)'", + new NpgsqlGeometryTypeMapping("geometry", isGeography: false).GenerateSqlLiteral(new Point(1, 2))); + + [Fact] + public void GenerateSqlLiteral_returns_point_literal_with_Z() + => Assert.Equal( + "GEOMETRY 'POINT Z(1 2 3)'", + new NpgsqlGeometryTypeMapping("geometry", isGeography: false).GenerateSqlLiteral(new Point(new CoordinateZ(1, 2, 3)))); + + [Fact] // #4093 + public void GenerateSqlLiteral_returns_point_literal_with_M() + => Assert.Equal( + "GEOMETRY 'POINT M(1 2 4)'", + new NpgsqlGeometryTypeMapping("geometry", isGeography: false).GenerateSqlLiteral(new Point(new CoordinateM(1, 2, 4)))); + + [Fact] // #4093 + public void GenerateSqlLiteral_returns_point_literal_with_Z_and_M() + => Assert.Equal( + "GEOGRAPHY 'SRID=4326;POINT ZM(1 2 3 4)'", + new NpgsqlGeometryTypeMapping("geography", isGeography: true).GenerateSqlLiteral( + new Point(new CoordinateZM(1, 2, 3, 4)) { SRID = 4326 })); +}