= ANY with an AND/OR item omits parentheses, returning wrong results

未关闭 适合新手
#4,122 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

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

调研方向

从 src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs 中约第 890 行的 VisitArrayAny 实现开始,然后检查现有的优先级处理及其测试。使用提供的 Contains 查询重现问题,并验证生成的 SQL 是否将 AND 表达式置于 = ANY 之前进行分组;添加或更新回归测试,以确认查询返回预期的行。

由索引模型根据 Issue 内容生成。

描述

VisitArrayAny writes the item without parentheses. When the item is an AND/OR expression, = binds tighter than AND, so Postgres applies = ANY to the last operand only. The query runs without error and returns the wrong rows.

bool[] availability = [false];

var unavailable = db.Products
    .Where(p => availability.Contains(p.IsListed && p.InStock));
SQL Result
Expected WHERE (p."IsListed" AND p."InStock") = ANY (@availability) Banana, Cherry, Date
Actual WHERE p."IsListed" AND p."InStock" = ANY (@availability) Banana

Seed: Apple listed and in stock, Banana listed and out of stock, Cherry not listed and in stock, Date neither.

Full repro
using Microsoft.EntityFrameworkCore;

await using var db = new ShopContext();
await db.Database.EnsureDeletedAsync();
await db.Database.EnsureCreatedAsync();

db.Products.AddRange(
    new Product { Name = "Apple", IsListed = true, InStock = true },
    new Product { Name = "Banana", IsListed = true, InStock = false },
    new Product { Name = "Cherry", IsListed = false, InStock = true },
    new Product { Name = "Date", IsListed = false, InStock = false });
await db.SaveChangesAsync();

bool[] availability = [false];

var query = db.Products
    .Where(p => availability.Contains(p.IsListed && p.InStock))
    .Select(p => p.Name);

Console.WriteLine(query.ToQueryString());
Console.WriteLine(string.Join(", ", await query.ToListAsync()));

public class ShopContext : DbContext
{
    public DbSet<Product> Products => Set<Product>();

    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseNpgsql("Host=localhost;Username=postgres;Password=postgres;Database=any_repro");
}

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public bool IsListed { get; set; }
    public bool InStock { get; set; }
}

Cause: NpgsqlQuerySqlGenerator.cs#L890 calls Visit(expression.Item) with no precedence check. The precedence table already ranks PgAnyExpression at 800 and AndAlso/OrElse at 500 (L1636).

Suggested fix, the same pattern as npgsql/npgsql#3927:

var requiresParentheses = RequiresParentheses(expression, expression.Item);
if (requiresParentheses)
{
    Sql.Append("(");
}

Visit(expression.Item);

if (requiresParentheses)
{
    Sql.Append(")");
}

Workaround: with a single picked value, filter on the expression or its negation (p.IsListed && p.InStock or !(p.IsListed && p.InStock)) in place of Contains.

Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3, PostgreSQL 17. Happy to send a PR if that helps.

主要语言
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 摘要。