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.

< Voltar