Start Debugging

EF Core 11 traduz Contains para JSON_CONTAINS no SQL Server 2025

EF Core 11 traduz automaticamente LINQ Contains sobre coleções JSON para a nova função JSON_CONTAINS do SQL Server 2025, e adiciona EF.Functions.JsonContains para queries com path e modos específicos que conseguem bater num índice JSON.

O SQL Server 2025 ganhou uma função nativa JSON_CONTAINS, e o EF Core 11 é o release que se conecta a ela. Duas coisas mudam para quem armazena coleções como colunas JSON: Contains sobre coleções JSON agora ganha uma tradução direta em vez do antigo join OPENJSON, e existe um novo EF.Functions.JsonContains() para casos em que você precisa de um path JSON ou um modo de busca específico. O trabalho faz parte do EF Core 11 Preview 3.

Optando pelo nível de compatibilidade do SQL Server 2025

A nova tradução só liga quando o provider sabe que está conversando com o SQL Server 2025. Você faz isso via UseCompatibilityLevel(170) nas opções do provider:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.UseSqlServer(
        connectionString,
        o => o.UseCompatibilityLevel(170));

O nível de compatibilidade 170 é o que o SQL Server 2025 reporta; níveis menores continuam usando a tradução antiga, então é seguro deixar de fora até você realmente atualizar o banco.

Como o Contains fica agora

Pegue uma forma clássica de “tags como array JSON”:

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public List<string> Tags { get; set; } = new();
}

modelBuilder.Entity<Blog>()
    .Property(b => b.Tags)
    .HasColumnType("json"); // SQL Server 2025 native JSON type

No EF Core 10 ou em um target SQL Server mais antigo, esta query:

var posts = await context.Blogs
    .Where(b => b.Tags.Contains("ef-core"))
    .ToListAsync();

devolve a tradução OPENJSON, que se lê como uma subquery correlacionada:

WHERE N'ef-core' IN (
    SELECT [t].[value]
    FROM OPENJSON([b].[Tags]) WITH ([value] nvarchar(max) '$') AS [t]
)

EF Core 11 contra o nível de compatibilidade 170 emite isso no lugar:

WHERE JSON_CONTAINS([b].[Tags], 'ef-core') = 1

A razão de isso importar não é só estética do SQL. JSON_CONTAINS é o único predicado no SQL Server 2025 que consegue usar um índice JSON. Se você tem CREATE JSON INDEX IX_Tags ON Blogs(Tags), o caminho OPENJSON nunca o toca, mas a tradução do EF 11 sim.

Tem uma armadilha apontada nas release notes: JSON_CONTAINS não trata NULL como o Contains do LINQ trata, então o EF só escolhe a nova tradução quando pelo menos um lado é comprovadamente não-anulável (uma constante não nula, ou uma coluna não anulável). Se ambos os lados podem ser null, o EF cai pra OPENJSON para preservar o comportamento existente.

Quando você precisa de um path ou um modo de busca

Contains cobre o caso “esse escalar está no array”. Para qualquer outra coisa, o EF Core 11 expõe EF.Functions.JsonContains(container, value, path?, mode?). O exemplo clássico é procurar um valor num path específico dentro de um documento JSON estruturado:

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public string JsonData { get; set; } = "{}"; // { "Rating": 8, ... }
}

var ratedEights = await context.Blogs
    .Where(b => EF.Functions.JsonContains(b.JsonData, 8, "$.Rating") == 1)
    .ToListAsync();

Traduz para:

WHERE JSON_CONTAINS([b].[JsonData], 8, N'$.Rating') = 1

Você pode usar com colunas string escalares, com tipos complexos mapeados em JSON, e com tipos owned mapeados via OwnsOne(... b.ToJson()). A comparação contra = 1 é load-bearing: JSON_CONTAINS retorna um bit, e o EF preserva isso para que predicados compostos como WHERE ... AND JSON_CONTAINS(...) = 1 continuem SARGable contra um índice JSON.

Combine isso com EF.Functions.JsonPathExists para checagens “essa propriedade existe?” e você cobre a maior parte da superfície de queries de coluna JSON sem descer para SQL cru. A lista completa de mudanças do tradutor do EF Core 11 está no doc What’s New.

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Voltar