NTS: the M ordinate is dropped from geometry SQL literals and from geometries stored in JSON

Abierto Apto para principiantes
#4,093 1 comentario 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

@Ahmed7991 ya está trabajando en esto.

Desde el 4/11/2026.

  • #4120 de @Ahmed7991 — abierto

Evaluación

Dificultad
2/5
Tiempo estimado
1-3 horas
Aptitud para principiantes
85/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
csharp, postgresql
Área
database

Línea de trabajo

El error está en src/EFCore.PG.NTS/Storage/Internal/NpgsqlGeometryTypeMapping.cs (línea 94) y NpgsqlJsonGeometryWktReaderWriter.cs (línea 33), donde geometry.AsText() y value.ToText() descartan M. Reemplaza estos con un WKTWriter configurado para emitir todas las coordenadas (X, Y, Z, M). Hecho significa que POINT ZM(1 2 3 4) sobrevive tanto a literales SQL como a idas y vueltas JSON, como se muestra en el repro.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

Environment
PostgreSQL 18.6 with PostGIS 3.6.4 and GEOS 3.14.1 (postgis/postgis:18-3.6), session TimeZone Etc/UTC
.NET 10 runtime 10.0.11, Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite 10.0.3, EF Core 10.0.4
.NET 11 runtime 11.0.0-rc.2, Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite 11.0.0-rc.2, EF Core 11.0.0-rc.2
Also Npgsql 10.0.3 on both lines, NetTopologySuite 2.6.0, Npgsql.NetTopologySuite 10.0.3, macOS arm64

Both lines print the same output. main at 150011d has the same code.

Geometry SQL literals and geometries inside JSON documents are written as WKT with NTS's default writer, which emits X, Y and Z only. A POINT ZM (1 2 3 4) becomes POINT Z(1 2 3).

Repro
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using NetTopologySuite.Geometries;

await using var context = new TestContext();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();

var zm = new Point(new CoordinateZM(1, 2, 3, 4));
context.Rows.Add(new Row { Maybe = null, Doc = new Doc { Location = zm } });
await context.SaveChangesAsync();

// 1. An inlined geometry constant is written as WKT without M
Console.WriteLine($"(Maybe ?? POINT ZM(1 2 3 4)).M: .NET {zm.M}, database {(await context.Rows.Select(r => (double?)(r.Maybe ?? new Point(new CoordinateZM(1, 2, 3, 4))).M).SingleAsync())?.ToString() ?? "NULL"}");

// 2. A geometry inside a JSON column is stored as WKT without M
await using var fresh = new TestContext();
Console.WriteLine($"JSON document Location.M after a round trip: written {zm.M}, read back {(await fresh.Rows.SingleAsync()).Doc.Location.M}");
Console.WriteLine($"Stored document: {await fresh.Database.SqlQueryRaw<string>("SELECT \"Doc\"::text AS \"Value\" FROM \"Rows\"").SingleAsync()}");

public class TestContext : DbContext
{
    public DbSet<Row> Rows => Set<Row>();

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseNpgsql(Environment.GetEnvironmentVariable("Test__Npgsql__DefaultConnection"), o => o.UseNetTopologySuite())
            .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.HasPostgresExtension("postgis");
        modelBuilder.Entity<Row>().ComplexProperty(r => r.Doc, d => d.ToJson());
    }
}

public class Row
{
    public int Id { get; set; }
    public Point? Maybe { get; set; }
    public Doc Doc { get; set; }
}

public class Doc
{
    public Point Location { get; set; }
}

Output, the same on both lines:

(Maybe ?? POINT ZM(1 2 3 4)).M: .NET 4, database NULL
JSON document Location.M after a round trip: written 4, read back NaN
Stored document: {"Location": "POINT Z(1 2 3)"}
SELECT ST_M(COALESCE(r."Maybe", GEOMETRY 'POINT Z(1 2 3)'))

The literal comes from NpgsqlGeometryTypeMapping.cs#L94 (geometry.AsText()) and the JSON value from NpgsqlJsonGeometryWktReaderWriter.cs#L33 (value.ToText()). A WKTWriter with all ordinates would keep M in both places. #3236 fixed the SRID part of the JSON writer.

Lenguaje dominante
C#
Estrellas
1.8k
Forks
268
Merge medio
2 h 35 min
PR fusionados (30 d)
136

Preparar el entorno

Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de npgsql/efcore.pg

Todos los issues de npgsql/efcore.pg

Issues similares

Más issues de C#

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.