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

Aperta Adatta ai principianti
#4,122 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
82/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
csharp, postgresql

Direzione di ricerca

Inizia in src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs, nell’implementazione di VisitArrayAny intorno alla riga 890, poi controlla la gestione esistente della precedenza e i relativi test. Riproduci il problema con la query Contains fornita e verifica che l’SQL generato raggruppi l’espressione AND prima di = ANY; aggiungi o aggiorna un test di regressione per confermare che la query restituisca le righe previste.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

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.

Lingua principale
C#
Stelle
1.8k
Fork
268
Merge medio
2h 35m
PR unite (30g)
136

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di npgsql/efcore.pg

Tutte le issue di npgsql/efcore.pg

Issue simili

Altre issue su C#

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.