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

Abierto Apto para principiantes
#4,122 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
2/5
Tiempo estimado
1-3 horas
Aptitud para principiantes
82/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
csharp, postgresql

Línea de trabajo

Empieza en src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs, en la implementación de VisitArrayAny alrededor de la línea 890, y luego revisa el tratamiento de precedencia existente y sus pruebas. Reproduce el problema con la consulta Contains proporcionada y verifica que el SQL generado agrupe la expresión AND antes de = ANY; añade o actualiza una prueba de regresión para confirmar que la consulta devuelve las filas esperadas.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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.

Lenguaje dominante
C#
Estrellas
1.8k
Forks
268
Merge medio
2 h 35 min
PR fusionados (30 d)
136

Preparar el entorno

Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de npgsql/efcore.pg

Todos los issues de npgsql/efcore.pg

Issues similares

Más issues de C#

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.