`Contains` on a JSON primitive collection compares JSON text, so `DateTime` and `TimeOnly` items never match

Offen
#4,099 1 Kommentar 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Maintainer antworten meist innerhalb von 1 Tag

@Mr444-dev arbeitet bereits daran.

Seit 03.11.2026.

  • #4118 von @Mr444-dev — offen

Bewertung

Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Anfängerfreundlichkeit
45/100
Issue-Typ
Bug
Klarheit
Größtenteils klar
Aktivitätsstatus
Aktiv
Tech-Stack
csharp, postgresql, sql
Bereich
database

Rechercherichtung

Start from src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs lines 756-766 where the JSON collection Contains is translated. Read the existing TODO and the JsonValueReaderWriter used for constants/parameters, and check how array unnesting is done elsewhere for typed comparison. Run the repro in the issue to see the mismatched SQL, then adjust the translation so the element type matches the stored JSON text.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Beschreibung

Environment
PostgreSQL 18.6 (postgres:18), session TimeZone Etc/UTC
.NET 10 runtime 10.0.11, Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3, EF Core 10.0.4
.NET 11 runtime 11.0.0-rc.2, Npgsql.EntityFrameworkCore.PostgreSQL 11.0.0-rc.2, EF Core 11.0.0-rc.2
Also Npgsql 10.0.3 on both lines, macOS arm64

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

LINQ Stored by EF .NET PostgreSQL SQL
r.Doc.Dates.Contains(ten) "2026-01-01T10:00:00Z" match no match (r."Doc" -> 'Dates') @> to_jsonb(@ten)
r.Doc.Times.Contains(new TimeOnly(10, 0)) "10:00:00.0000000" match no match (r."Doc" -> 'Times') @> to_jsonb(TIME '10:00:00')

to_jsonb() of the item gives "2026-01-01T10:00:00+00:00" and "10:00:00", not the text EF wrote into the document, and @> compares the strings.

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

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

var ten = new DateTime(2026, 1, 1, 10, 0, 0, DateTimeKind.Utc);
context.Rows.Add(new Row { Doc = new Doc { Dates = [ten], Times = [new TimeOnly(10, 0)] } });
await context.SaveChangesAsync();
var rows = await context.Rows.AsNoTracking().ToListAsync();

Console.WriteLine($"Doc.Dates.Contains(10:00 UTC): .NET {rows.Count(r => r.Doc.Dates.Contains(ten))}, database {await context.Rows.CountAsync(r => r.Doc.Dates.Contains(ten))}");
Console.WriteLine($"Doc.Times.Contains(10:00): .NET {rows.Count(r => r.Doc.Times.Contains(new TimeOnly(10, 0)))}, database {await context.Rows.CountAsync(r => r.Doc.Times.Contains(new TimeOnly(10, 0)))}");
Console.WriteLine($"Stored document: {await context.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"))
            .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();

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

public class Row
{
    public int Id { get; set; }
    public Doc Doc { get; set; } = new();
}

public class Doc
{
    public List<DateTime> Dates { get; set; } = [];
    public List<TimeOnly> Times { get; set; } = [];
}

Output, the same on both lines:

Doc.Dates.Contains(10:00 UTC): .NET 1, database 0
Doc.Times.Contains(10:00): .NET 1, database 0
Stored document: {"Dates": ["2026-01-01T10:00:00Z"], "Times": ["10:00:00.0000000"]}

The translation is NpgsqlQueryableMethodTranslatingExpressionVisitor.cs#L756-L766. Its TODO already mentions producing the JSON value on the client with the JsonValueReaderWriter for constants and parameters, which would fix it for those. For a column operand, the comparison would have to be typed (unnest the array and compare as the element type).

Vorherrschende Sprache
C#
Sterne
1.8k
Forks
268
Ø Merge
2 Std. 35 Min.
Gemergte PRs (30 T.)
136

Entwicklungsumgebung

Dieses Projekt bietet weder Dev-Container noch Dockerfile noch Beitragsleitfaden – die Einrichtung liegt bei Ihnen. Beginnen Sie mit der README; die allgemeinen Schritte stehen in unserem Leitfaden für den ersten Beitrag.

Erste Schritte

  1. Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
  3. Forken Sie das Repository und arbeiten Sie in einem Branch.
  4. Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.

Mehr aus npgsql/efcore.pg

Alle Issues in npgsql/efcore.pg

Ähnliche Issues

Weitere Issues zu C#

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.