AAAlison AmorimBlog
alisonamorim.com ↗

Performance

Entity Framework Core 10: Transformando a Forma Como Você Interage com Dados

As novidades do EF Core 10 com exemplos: tipos complexos e JSON, LeftJoin e RightJoin, busca vetorial, filtros nomeados e ExecuteUpdateAsync com lambdas.

  • 7 min de leitura
  • Performance
Fonte do texto
Paisagem synthwave abstrata com grade neon, sol no horizonte e cubos e poliedros flutuando
Neste artigo · 10 seções
  1. 1. Tipos Complexos e Mapeamento JSON: Uma Revolução na Modelagem de Dados
  2. 2. LeftJoin e RightJoin: Finalmente, LINQ Matches SQL
  3. 3. Buscas Vetoriais: Entrando na Era da IA
  4. 4. Suporte Aprimorado ao Tipo JSON do SQL Server 2025
  5. 5. Filtros de Query Nomeados: Controle Fino sobre Filtros Globais
  6. 6. ExecuteUpdateAsync com Lambdas Não-Expressão
  7. 7. Melhorias na Tradução de LINQ: Coleções Parametrizadas e Muito Mais
  8. 8. Melhorias em Segurança e Performance
  9. Conclusão
  10. Insights & Takeaways

Entity Framework Core tem sido a solução go-to para acesso a dados no .NET desde sua concepção. A cada versão, a plataforma evolui não apenas adicionando novos recursos, mas respondendo aos desafios reais que desenvolvedores enfrentam no dia a dia. O Entity Framework Core 10 (EF Core 10), lançado em novembro de 2025 como uma versão Long-Term Support, representa um ponto de inflexão significativo nessa evolução.

As melhorias introduzidas nesta versão não são apenas incrementais - elas redefinem a forma como desenvolvemos aplicações orientadas a dados, tornando operações complexas simples, queries performáticas intuitivas, e estruturas de dados sofisticadas naturais de expressar. Este artigo explora as principais melhorias do EF Core 10 de forma didática e prática, mostrando como cada recurso resolve problemas concretos e como você pode começar a aproveitá-los imediatamente em seus projetos.

1. Tipos Complexos e Mapeamento JSON: Uma Revolução na Modelagem de Dados

Por anos, os desenvolvedores do EF Core enfrentavam um dilema fundamental: como modelar dados estruturados que não são entidades independentes? A resposta existia, mas era complicada. Você tinha que usar owned entities, que funcionavam, mas vinham com limitações frustrantes e comportamentos inesperados.

O EF Core 10 introduz tipos complexos como primeiro conceito da linguagem, oferecendo uma modelagem muito mais natural e intuitiva. A diferença fundamental é que tipos complexos não têm identidade - eles existem apenas como parte de uma entidade maior. Essa simplicidade conceitual resolve diversos problemas que entidades possuídas criavam.

Mapeamento com Table Splitting

Imagine um aplicativo de e-commerce onde clientes têm um endereço de envio e outro de cobrança. Com tipos complexos, a modelagem fica elegante e natural:

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Address ShippingAddress { get; set; }
    public Address? BillingAddress { get; set; }
}

public class Address
{
    public required string Street { get; set; }
    public required string City { get; set; }
    public required string ZipCode { get; set; }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Customer>(b =>
    {
        b.ComplexProperty(c => c.ShippingAddress);
        b.ComplexProperty(c => c.BillingAddress);
    });
}

O EF Core gera automaticamente a seguinte tabela:

CREATE TABLE [Customers] (
    [Id] int NOT NULL IDENTITY,
    [Name] nvarchar(max) NOT NULL,
    [ShippingAddress_Street] nvarchar(max) NOT NULL,
    [ShippingAddress_City] nvarchar(max) NOT NULL,
    [ShippingAddress_ZipCode] nvarchar(max) NOT NULL,
    [BillingAddress_Street] nvarchar(max),
    [BillingAddress_City] nvarchar(max),
    [BillingAddress_ZipCode] nvarchar(max),
    CONSTRAINT [PK_Customers] PRIMARY KEY ([Id])
);

O que torna isso especial é que tipos complexos têm semântica de valor. Diferente de entidades possuídas, você pode atribuir um endereço a outro livremente sem erros.

Mapeamento JSON: A Verdadeira Revolução

Mas a verdadeira revolução chega com mapeamento JSON. O EF Core 10 permite mapear tipos complexos diretamente para colunas JSON, especialmente com o novo tipo json do SQL Server 2025:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Customer>(b =>
    {
        b.ComplexProperty(c => c.ShippingAddress, c => c.ToJson());
        b.ComplexProperty(c => c.BillingAddress, c => c.ToJson());
    });
}

Agora cada endereço é armazenado como uma coluna JSON individual:

CREATE TABLE [Customers] (
    [Id] int NOT NULL IDENTITY,
    [Name] nvarchar(max) NOT NULL,
    [ShippingAddress] json NOT NULL,
    [BillingAddress] json,
    CONSTRAINT [PK_Customers] PRIMARY KEY ([Id])
);

A mágica está em como você interage com esses dados. LINQ queries funcionam perfeitamente com propriedades aninhadas:

var customersWithFreeShipping = await context.Customers
    .Where(c => c.ShippingAddress.City == "New York")
    .ToListAsync();

O EF Core traduz isso para SQL elegante usando JSON_VALUE:

SELECT [c].[Id], [c].[Name], [c].[ShippingAddress], [c].[BillingAddress]
FROM [Customers] AS [c]
WHERE JSON_VALUE([c].[ShippingAddress], '$.City') = N'New York'

Suporte para Structs

O EF Core 10 estende tipos complexos para incluir structs .NET, oferecendo semântica de valor ainda mais explícita:

public struct Address
{
    public required string Street { get; set; }
    public required string City { get; set; }
    public required string ZipCode { get; set; }
}

Isso alinha perfeitamente com a natureza sem-identidade de tipos complexos e reduz a pegada de memória quando esses tipos são usados em coleções.

2. LeftJoin e RightJoin: Finalmente, LINQ Matches SQL

Um dos mistérios duradouros do LINQ era como expressar uma operação tão comum quanto LEFT JOIN. Desenvolvedores frequentemente precisavam consultar documentação ou Stack Overflow para se lembrar da combinação mágica de GroupJoin, SelectMany e DefaultIfEmpty que criava a semântica de left join.

// Antes do EF Core 10: O caminho confuso
var query = dbContext.Products
    .GroupJoin(
        dbContext.Reviews,
        product => product.Id,
        review => review.ProductId,
        (product, reviewList) => new { product, subgroup = reviewList }
    )
    .SelectMany(
        joinedSet => joinedSet.subgroup.DefaultIfEmpty(),
        (joinedSet, review) => new
        {
            ProductId = joinedSet.product.Id,
            joinedSet.product.Name,
            ReviewId = (int?)review.Id ?? 0
        }
    );

Essa abordagem funcionava, mas a intenção era obscura e o código era verbose. A partir do .NET 10, o LINQ ganhou suporte nativo para LeftJoin e RightJoin, e o EF Core reconhece e traduz esses métodos perfeitamente:

// Depois do EF Core 10: Claro e conciso
var query = dbContext.Products
    .LeftJoin(
        dbContext.Reviews,
        product => product.Id,
        review => review.ProductId,
        (product, review) => new
        {
            ProductId = product.Id,
            product.Name,
            ReviewId = (int?)review.Id ?? 0
        }
    );

O SQL gerado é idêntico, mas o código expressa sua intenção muito mais claramente. Da mesma forma, RightJoin permite manter todos os dados da segunda coleção enquanto filtra a primeira, traduzindo para RIGHT JOIN no SQL.

A beleza dessa adição é que ela remove uma fricção fundamental que causava desenvolvedores a evitar left joins ou a recorrer a queries SQL bruto quando queriam que fossem corretamente traduzidas pelo EF.

3. Buscas Vetoriais: Entrando na Era da IA

O EF Core 10 traz suporte completo para buscas vetoriais em SQL Server 2025 e Azure SQL Database, marcando a integração da plataforma com fluxos de trabalho modernos baseados em IA. Embeddings - representações numéricas de significado que podem ser comparadas para similaridade - são agora cidadãos de primeira classe no EF Core.

Você simplesmente adiciona uma propriedade de tipo SqlVector<float> à sua entidade:

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }

    [Column(TypeName = "vector(1536)")]
    public SqlVector<float> Embedding { get; set; }
}

E pode realizar consultas de similaridade usando EF.Functions.VectorDistance:

var searchVector = /* embedding gerado de uma query de usuário */;

var similarBlogs = await context.Blogs
    .OrderBy(b => EF.Functions.VectorDistance("cosine", b.Embedding, searchVector))
    .Take(5)
    .ToListAsync();

Para cenários ainda mais sofisticados, o EF Core 10 introduz buscas híbridas, combinando busca vetorial com busca de texto completo usando a função RRF (Reciprocal Rank Fusion):

var hybrid = await context.Blogs
    .OrderBy(x => EF.Functions.Rrf(
        EF.Functions.FullTextScore(x.Contents, "database"),
        EF.Functions.VectorDistance("cosine", x.Embedding, myVector)
    ))
    .Take(10)
    .ToListAsync();

4. Suporte Aprimorado ao Tipo JSON do SQL Server 2025

Enquanto suporte a JSON existia no EF Core anteriormente, era limitado. O SQL Server 2025 introduz um tipo de dados json nativo que oferece eficiência e segurança significativamente melhores em comparação com armazenar JSON em colunas nvarchar.

O EF Core 10 reconhece automaticamente quando você está usando SQL Server 2025 ou Azure SQL Database e padrão para usar o novo tipo json:

public class Blog
{
    public int Id { get; set; }
    public string[] Tags { get; set; }
    public BlogDetails Details { get; set; }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .ComplexProperty(b => b.Details, b => b.ToJson());
}

O EF Core gera:

CREATE TABLE [Blogs] (
    [Id] int NOT NULL IDENTITY,
    [Tags] json NOT NULL,
    [Details] json NOT NULL
);

Mas o verdadeiro poder está em como você interage com JSON. Você pode filtrar, ordenar e até indexar propriedades JSON aninhadas:

modelBuilder.Entity<Blog>()
    .HasIndex(b => b.Details.Viewers)
    .IsUnique();

var blogs = await context.Blogs
    .Where(b => b.Details.Viewers > 100)
    .ToListAsync();

5. Filtros de Query Nomeados: Controle Fino sobre Filtros Globais

Query filters do EF Core sempre foram poderosos para implementar padrões como soft delete ou multi-tenancy, mas tinham uma limitação fundamental: você só podia ter um filtro por tipo de entidade.

O EF Core 10 introduz filtros de query nomeados, permitindo múltiplos filtros que podem ser gerenciados independentemente:

modelBuilder.Entity<Blog>()
    .HasQueryFilter("SoftDeletionFilter", b => !b.IsDeleted)
    .HasQueryFilter("TenantFilter", b => b.TenantId == currentTenantId);

Agora você pode desabilitar seletivamente filtros específicos:

// Mostrar todos os blogs (ignorando o filtro de soft delete, mas mantendo o de tenant)
var allBlogs = await context.Blogs
    .IgnoreQueryFilters(["SoftDeletionFilter"])
    .ToListAsync();

A recomendação é usar enums ou constantes para nomes de filtros, não hardcode strings:

enum QueryFilters
{
    SoftDeletion = 0,
    Tenant = 1
}

modelBuilder.Entity<Blog>()
    .HasQueryFilter(QueryFilters.SoftDeletion.ToString(), b => !b.IsDeleted)
    .HasQueryFilter(QueryFilters.Tenant.ToString(), b => b.TenantId == tenantId);

6. ExecuteUpdateAsync com Lambdas Não-Expressão

ExecuteUpdateAsync é uma ferramenta poderosa para bulk updates, mas historicamente tinha uma limitation severa: ela aceitava apenas lambdas de expressão, o que tornava lógica condicional complexa praticamente impossível de expressar de forma legível.

// Antes do EF Core 10: Construir árvores de expressão manualmente
if (nameChanged)
{
    // ... complexa construção de árvore de expressão ...
    var setters = Expression.Lambda<Func<SetPropertyCalls<Blog>, SetPropertyCalls<Blog>>>(
        Expression.Call(/* ... */),
        setters.Parameters
    );
    await context.Blogs.ExecuteUpdateAsync(setters);
}

O EF Core 10 permite lambdas regulares, transformando isso em operação simples e direta:

await context.Blogs.ExecuteUpdateAsync(s =>
{
    s.SetProperty(b => b.Views, 8);
    if (nameChanged)
    {
        s.SetProperty(b => b.Name, "foo");
    }
});

Isso abre possibilidades que antes eram impraticáveis, permitindo bulk updates complexos com lógica dinâmica.

7. Melhorias na Tradução de LINQ: Coleções Parametrizadas e Muito Mais

Uma categoria inteira de melhorias foi feita na tradução de LINQ para SQL. Anteriormente, trabalhar com coleções parametrizadas criava problemas de plan cache ao inline-ar valores, ou perdia informações de cardinalidade ao usar JSON.

O EF Core 10 introduz um novo modo padrão que parametriza cada valor em um parâmetro separado, permitindo mudanças de valores sem alterar o SQL, enquanto fornece ao otimizador informações sobre cardinalidade:

int[] ids = [1, 2, 3];
var blogs = await context.Blogs
    .Where(b => ids.Contains(b.Id))
    .ToListAsync();

Antes geraria:

SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Id] IN (1, 2, 3)  -- Valores inline

Ou com JSON:

WHERE [b].[Id] IN (
    SELECT [i].[value]
    FROM OPENJSON(@__ids_0) WITH ([value] int '$') AS [i]
)

O EF Core 10 gera:

SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Id] IN (@ids1, @ids2, @ids3, @ids4, @ids5)

Cada valor é seu próprio parâmetro, o SQL permanece o mesmo independentemente dos valores, e o otimizador sabe que há exatamente 5 valores.

Além disso, novas traduções foram adicionadas para funções de data/hora, operações com Span, e muitas outras operações que antes não eram traduzidas.

8. Melhorias em Segurança e Performance

O EF Core 10 introduz vários melhoramentos relacionados a segurança. Constantes inlined agora são redatadas no logging por padrão, protegendo informações potencialmente sensíveis em dados de diagnóstico. Além disso, um novo analisador avisa sobre possíveis problemas de SQL injection ao usar APIs SQL bruto com concatenação de string.

Em termos de performance, queries divididas agora mantêm ordenação consistente, evitando corrupção de dados em cenários complexos onde múltiplas queries são necessárias. Várias operações foram otimizadas, incluindo melhor manipulação de LIMIT e operações COUNT.

Conclusão

O Entity Framework Core 10 representa a maturidade de uma plataforma que ouve seus usuários e responde com soluções elegantes para problemas reais. Tipos complexos oferecem uma alternativa muito melhor a entidades possuídas, eliminando anos de frustrações com referência duplicada. LeftJoin e RightJoin finalmente tornam operações comuns tão simples quanto deveriam ser. Suporte a vectors e buscas híbridas integra a plataforma com o futuro da IA. Filtros de query nomeados oferecem controle fino sobre comportamentos globais. Bulk updates com lógica condicional agora são práticos e legíveis.

Essas melhorias não são isoladas - elas trabalham juntas, amplificando uma à outra. Tipos complexos com mapeamento JSON permitem modelagem de dados verdadeiramente moderna sem necessidade de JOINs complexos. Buscas vetoriais abrem novos cenários de aplicação completamente inviáveis antes. Filtros nomeados e ExecuteUpdateAsync melhorado permitem padrões de código anteriormente impossíveis. O EF Core 10 prova que a plataforma continua evoluindo para atender aos desafios contemporâneos enquanto mantém a elegância e produtividade que sempre a definiram.

Insights & Takeaways

  • Tipos complexos com value semantics oferecem uma alternativa muito mais limpa a owned entities, eliminando problemas históricos com referência e identidade.
  • Mapeamento JSON nativo transforma estruturas de dados complexas em queries LINQ simples, eliminando a necessidade de JOINs e melhorando performance.
  • LeftJoin e RightJoin resolvem um dos maiores pontos de fricção do LINQ, tornando operações comuns expressivas e intuitivas.
  • Buscas vetoriais e híbridas integram EF Core com fluxos de trabalho modernos baseados em IA, suportando embeddings e RAG nativamente.
  • Filtros de query nomeados oferecem controle fino sobre filtros globais, resolvendo o problema de ter que aplicar "todos ou nenhum" que frustrava desenvolvedores de aplicações complexas.

Comente no LinkedIn

A conversa sobre este artigo acontece no LinkedIn, onde ele também foi publicado. Discordou de algo, viu de outro jeito em produção? Conta lá.

Leia também