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

オープン
#4,099 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

@Mr444-dev がすでに取り組んでいます。

2026年11月3日 から。

  • #4118 @Mr444-dev による — オープン

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
45/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
活発
技術スタック
csharp, postgresql, sql
領域
database

調査の方向性

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.

索引モデルが issue の本文から書いたものです。

説明

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).

主要言語
C#
スター
1.8k
フォーク
268
平均マージ
2時間 35分
マージ済み PR(30日)
136

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

npgsql/efcore.pg のほかの issue

npgsql/efcore.pg の issue をすべて見る

似ている issue

C# の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。