`ExecuteUpdate` copying an out-of-range JSON array element sets the whole JSON column to NULL

未关闭
#4,097 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
3/5
预计耗时
1-2 天
新手友好度
70/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
csharp, postgresql
领域
databases

调研方向

Start in src/EFCore.PG/Query/Internal/NpgsqlQueryableMethodTranslatingExpressionVisitor.cs at the setter (lines 1405-1416) and the element nullability checks (lines 926-931, 1490). Run the C# repro from the issue to observe the JSON column becoming NULL. Done means an out-of-range JSON array index in ExecuteUpdate sets the target property to null, not the whole document to NULL.

由索引模型根据 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.

// Doc is {"Age": 1, "Name": "keep-me", "Nums": [1, 2]}, so Nums[5] is out of range
await context.Rows.ExecuteUpdateAsync(s => s.SetProperty(r => r.Doc!.Age, r => (int?)r.Doc!.Nums[5]));
UPDATE "Rows" AS r
SET "Doc" = jsonb_set(r."Doc", '{Age}', r."Doc" #> '{Nums,5}')

r."Doc" #> '{Nums,5}' is NULL, and jsonb_set(doc, path, NULL) returns NULL, so the whole document is gone. At worst, Age should become null.

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

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

context.Rows.Add(new Row { Doc = new Doc { Name = "keep-me", Age = 1, Nums = [1, 2] } });
await context.SaveChangesAsync();

// Nums has two elements, so Nums[5] is out of range: Age should become null, at worst
await context.Rows.ExecuteUpdateAsync(s => s.SetProperty(r => r.Doc!.Age, r => (int?)r.Doc!.Nums[5]));

Console.WriteLine($"Document after the update: {await context.Database.SqlQueryRaw<string>("""SELECT coalesce("Doc"::text, 'NULL') 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; }
}

public class Doc
{
    public string Name { get; set; } = "";
    public int? Age { get; set; }
    public List<int> Nums { get; set; } = [];
}

Output, the same on both lines:

Document after the update: NULL

The element access is created as non-nullable (isElementNullable, NpgsqlQueryableMethodTranslatingExpressionVisitor.cs#L926-L931), so MayReturnNull (NpgsqlQueryableMethodTranslatingExpressionVisitor.cs#L1490) says no, and the setter (NpgsqlQueryableMethodTranslatingExpressionVisitor.cs#L1405-L1416) uses plain jsonb_set, without COALESCE(..., 'null') and without jsonb_set_lax. #3850 fixed the sources that are visibly nullable; an out-of-range index is still unprotected. Treating JSON element access as nullable in the setter would close this.

主要语言
C#
星标
1.8k
派生
268
平均合并
2 小时 35 分钟
30 天内合并 PR
136

环境准备

这个项目没有提供开发容器、Dockerfile 或贡献指南,环境需要你自己搭建:先看它的 README,通用步骤见我们的新手贡献指南。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

npgsql/efcore.pg 的其他 Issue

查看 npgsql/efcore.pg 的全部 Issue

相似的 Issue

更多 C# Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。