<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[Martijn Laarman - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Martijn Laarman - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/pt/search-labs/author/martijn-laarman</link>
    </image>
    <link>https://www.elastic.co/pt/search-labs/author/martijn-laarman</link>
    <atom:link href="https://www.elastic.co/pt/search-labs/rss/author/martijn-laarman.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[pt]]></language>
    <lastBuildDate>Mon, 28 Sep 2026 21:52:19 GMT</lastBuildDate>
  <item>
    <title><![CDATA[LINQ para Elasticsearch ES|QL: escreva Consultas em C# e Consulte o Elasticsearch]]></title>
    <description><![CDATA[Explorando o novo provedor LINQ para Elasticsearch ES|QL no cliente Elasticsearch .NET, que permite escrever código C# automaticamente traduzido para consultas ES|QL.]]></description>
    <content:encoded><![CDATA[<p>A partir das <strong>versões 9.3.4</strong> e <strong>8.19.18</strong>, o cliente Elasticsearch .NET inclui um provedor de <a href="https://learn.microsoft.com/en-us/dotnet/csharp/linq/">Consulta Integrada em Linguagem (LINQ) </a>que traduz expressões LINQ em C# para a <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql.html">Linguagem de Consulta Elasticsearch (ES|QL)</a> em tempo de execução. Em vez de escrever manualmente as strings ES|QL, você compõe consultas usando <code>Where</code>, <code>Select</code>, <code>OrderBy</code>, <code>GroupBy</code> e outros operadores padrão. O provedor cuida da tradução, parametrização e desserialização dos resultados, inclusive o streaming por linha que mantém o uso da memória constante, independentemente do tamanho do conjunto de resultados.</p><h2>Sua primeira consulta</h2><p>Comece definindo um objeto CLR simples (POCO) que mapeia para o seu índice Elasticsearch. Os nomes das propriedades são resolvidos para nomes de coluna ES|QL via atributos <code>System.Text.Json</code> padrão, como <code>[JsonPropertyName]</code>, ou via <code>JsonNamingPolicy</code> configurado. As mesmas regras <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/source-serialization">de serialização de origem</a> que se aplicam ao restante do cliente também se aplicam aqui.</p>using System.Text.Json.Serialization;

public class Product
{
    [JsonPropertyName("product_id")]
    public string Id { get; set; }

    public string Name { get; set; }

    public string Brand { get; set; }

    [JsonPropertyName("price_usd")]
    public double Price { get; set; }

    [JsonPropertyName("in_stock")]
    public bool InStock { get; set; }
}<p>Com o tipo definido, uma consulta fica assim:</p>var minPrice = 100.0;
var brand = "TechCorp";

await foreach (var product in client.Esql.QueryAsync&lt;Product&gt;(q =&gt; q
    .From("products")
    .Where(p =&gt; p.InStock &amp;&amp; p.Price &gt;= minPrice &amp;&amp; p.Brand == brand)
    .OrderByDescending(p =&gt; p.Price)
    .Take(10)))
{
    Console.WriteLine($"{product.Name}: ${product.Price}");
}<p>O provedor traduz isso para o seguinte ES|QL:</p><p>Há alguns detalhes a serem observados:</p><ul><li><p><strong>Resolução do nome da propriedade:</strong> <code>p.Price</code> se torna <code>price_usd</code> por causa do atributo <code>[JsonPropertyName]</code>, e <code>p.Brand</code> se torna <code>brand</code> seguindo a política de nomenclatura padrão camelCase.</p></li><li><p><strong>Captura de parâmetros:</strong> As variáveis C# <code>minPrice</code> e <code>brand</code> são capturadas como parâmetros nomeados (<code>?minPrice</code>, <code>?brand</code>). Eles são enviados separadamente da string de consulta na carga JSON, o que evita injeções e permite o armazenamento em cache do plano de consulta no lado do servidor.</p></li><li><p><strong>Streaming:</strong> <code>QueryAsync&lt;T&gt;</code> retorna <code>IAsyncEnumerable&lt;T&gt;</code>. As linhas são materializadas uma de cada vez à medida que chegam do Elasticsearch.</p></li></ul><p>Você também pode inspecionar a consulta gerada e seus parâmetros sem executá-la:</p>var query = client.Esql.CreateQuery&lt;Product&gt;()
    .Where(p =&gt; p.InStock &amp;&amp; p.Price &gt;= minPrice &amp;&amp; p.Brand == brand)
    .OrderByDescending(p =&gt; p.Price)
    .Take(10);

Console.WriteLine(query.ToEsqlString());
// FROM products | WHERE (in_stock == true AND price_usd &gt;= 100) | SORT price_usd DESC | LIMIT 10

Console.WriteLine(query.ToEsqlString(inlineParameters: false));
// FROM products | WHERE (in_stock == true AND price_usd &gt;= ?minPrice AND brand == ?brand) | SORT price_usd DESC | LIMIT 10

var parameters = query.GetParameters();
// { "minPrice": 100.0, "brand": "TechCorp" }<h2>Como funciona? Uma breve revisão sobre o LINQ</h2><p>O mecanismo que torna possíveis os provedores LINQ é a distinção entre <code>IEnumerable&lt;T&gt;</code> e <code>IQueryable&lt;T&gt;</code>.</p><p>Quando você chama <code>.Where(p =&gt; p.Price &gt; 100)</code> em um <code>IEnumerable&lt;T&gt;</code>, o lambda compila para um <code>Func&lt;Product, bool&gt;</code>, um delegado regular que o runtime executa em processo. Isto é LINQ-to-Objects.</p><p>Quando você chama o mesmo método em um <code>IQueryable&lt;T&gt;</code>, o compilador C# envolve o lambda em um <code>Expression&lt;Func&lt;Product, bool&gt;&gt;</code> em vez disso. Essa é uma estrutura de dados que representa a <em>estrutura</em> do código em vez de sua forma executável. A árvore de expressões pode ser inspecionada, analisada e traduzida para outra linguagem em tempo de execução.</p>// IEnumerable: the lambda is a compiled delegate
IEnumerable&lt;Product&gt; local = products.Where(p =&gt; p.Price &gt; 100);

// IQueryable: the lambda is an expression tree, a data structure
IQueryable&lt;Product&gt; remote = queryable.Where(p =&gt; p.Price &gt; 100);<p>A interface <code>IQueryProvider</code> é o ponto de extensão. Qualquer provedor pode implementar <code>CreateQuery&lt;T&gt;</code> e <code>Execute&lt;T&gt;</code> para traduzir essas árvores de expressão para um idioma de destino. O Entity Framework usa isso para emitir SQL. O provedor LINQ to ES|QL o usa para emitir ES|QL.</p><p>A árvore de expressões para a consulta acima fica assim:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt521838e8b9c36649/6a1705b1839dfa5f40dcfdfe/f864cd18a390831f8d28503a29b5835efb1842f7-1000x720.png" alt="Árvore de expressões para a consulta de exemplo." /><p><em>Árvore de expressões para a consulta de exemplo.</em></p><p>A árvore é aninhada do avesso: <code>Take</code> envolve <code>OrderByDescending</code>, que envolve <code>Where</code>, que envolve <code>From</code>, que envolve a raiz <code>EsqlQueryable&lt;Product&gt;</code> constante. O predicado <code>Where</code> é ele próprio uma subárvore de <code>BinaryExpression</code> nós para os operadores <code>&amp;&amp;</code>, <code>&gt;=</code> e <code>==</code>, com folhas <code>MemberExpression</code> para acessos a propriedades e capturas de fechamento para as variáveis <code>minPrice</code> e <code>brand</code>. Essa é a estrutura de dados que o provedor percorre para produzir o ES|QL final.</p><h2>Nos bastidores: O pipeline de tradução</h2><p>O caminho de uma expressão LINQ até os resultados da consulta segue um pipeline de seis estágios:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt930670a505dd61ea/6a1705b3b339d58a54769ecf/2a2c772b63d720f61fc9a28b2f85668fa2db8d38-1999x1036.png" alt="Visão geral do pipeline de tradução." /><p><em>Visão geral do pipeline de tradução.</em></p><h3>1. Captura da árvore de expressão</h3><p>Quando você encadeia <code>.Where()</code>, <code>.OrderBy()</code>, <code>.Take()</code> e outros operadores em um <code>IQueryable&lt;T&gt;</code>, a infraestrutura padrão do LINQ constrói uma árvore de expressões. <code>EsqlQueryable&lt;T&gt;</code> implementa <code>IQueryable&lt;T&gt;</code> e delega para <code>EsqlQueryProvider</code>.</p><h3>2. Tradução</h3><p>Quando a consulta é executada (enumerando, chamando <code>ToList()</code> ou usando <code>await foreach)</code>), o <code>EsqlExpressionVisitor</code> percorre a árvore de expressões de dentro para fora. Ele despacha cada chamada de método LINQ para um visitante especializado:</p><p>Visitante</p><p>Traduz</p><p>Para</p><p>WhereClauseVisitor</p><p>.Where(predicate)</p><p>Condição ONDE</p><p>SelectProjectionVisitor</p><p>.Select(selector)</p><p>EVAL + KEEP + RENOMEAR</p><p>GroupByVisitor</p><p>.GroupBy().Select()</p><p>ESTATÍSTICAS ... POR</p><p>OrderByVisitor</p><p>.OrderBy() / .ThenBy()</p><p>Campo SORT [ASC\|DESC]</p><p>EsqlFunctionTranslator</p><p>EsqlFunctions.*, Math.*, métodos de string</p><p>80+ funções ES|QL</p><p>Durante a tradução, as variáveis C# referenciadas em expressões são capturadas como parâmetros nomeados.</p><h3>3. Modelo de consulta</h3><p>Os visitantes não produzem diretamente as strings. Em vez disso, produzem <code>QueryCommand</code> objetos, uma representação intermediária imutável. Um <code>FromCommand</code>, um <code>WhereCommand</code>, um <code>SortCommand</code>, e um <code>LimitCommand</code>, cada um representando um comando de processamento ES|QL. Eles são coletados para um modelo <code>EsqlQuery</code>.</p><p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt788c9936976f2f62/6a1705b50e2e4910da419ff0/2adc349b6cf655b96b7b3e826a134e8a17fe42fd-1999x1036.png" alt="Modelo de consulta e padrão de comando." /><p><em>Modelo de consulta e padrão de comando.</em></p><p>Esse modelo intermediário é desacoplado tanto da árvore de expressão quanto do formato de saída. Ele pode ser inspecionado, interceptado (via <code>IEsqlQueryInterceptor</code>) ou modificado antes da formatação.</p><h3>4. Formatação</h3><p><code>EsqlFormatter</code> visita cada <code>QueryCommand</code> em ordem e gera a string final do ES|QL. Cada comando se transforma em uma linha, separada pelo operador pipe (|), que o ES|QL utiliza para encadear comandos de processamento. Identificadores que contêm caracteres especiais são automaticamente escapados com backticks.</p><h3>5. Execução</h3><p>A string ES|QL formatada e os parâmetros capturados são enviados para o endpoint <code>/_query</code> do Elasticsearch no corpo da requisição, como JSON. A interface <code>IEsqlQueryExecutor</code> abstrai a camada de transporte, e é aí que a arquitetura de pacotes em camadas se aplica.</p><h3>6. Materialização</h3><p><code>EsqlResponseReader</code> transmite a resposta JSON sem armazenar todo o conjunto de resultados na memória. Uma árvore <code>ColumnLayout</code> , pré-computada uma vez por consulta, mapeia ES|QL nomes de colunas (como <code>address.street</code>, <code>address.city</code>) para propriedades aninhadas do POCO. Cada linha é montada em uma instância <code>T</code> e gerada uma de cada vez via <code>IEnumerable&lt;T&gt;</code> ou <code>IAsyncEnumerable&lt;T&gt;</code>.</p><h2>A arquitetura em camadas</h2><p>A funcionalidade LINQ para ES|QL é dividida em três pacotes:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt662bd0dd8861b6b6/6a1705b7a929cf7086ae08a2/41b8aae860ecdc2480edcb1c1d4cc9b03cfb78c9-1999x1036.png" alt="Arquitetura do pacote." /><p><em>Arquitetura de pacotes.</em>
<a href="https://www.nuget.org/packages/Elastic.Esql"><strong><code>Elastic.Esql</code></strong></a> é o motor de tradução puro. Ele não tem dependência HTTP e contém os visitantes de expressões, o modelo de consulta, o formatador e o leitor de resposta. Você pode usá-lo de forma independente para criar e inspecionar consultas ES|QL sem nenhuma conexão com o Elasticsearch. Isso é útil para testes, logging de consultas ou para criar a sua camada de execução.</p>// Translation-only: no Elasticsearch connection needed
var provider = new EsqlQueryProvider();
var query = new EsqlQueryable&lt;Product&gt;(provider)
    .From("products")
    .Where(p =&gt; p.InStock)
    .OrderByDescending(p =&gt; p.Price);

Console.WriteLine(query.ToEsqlString());
// FROM products | WHERE in_stock == true | SORT price_usd DESC<p><a href="https://www.nuget.org/packages/Elastic.Clients.Esql"><strong><code>Elastic.Clients.Esql</code></strong></a> é um cliente ES|QL leve e independente. Ele adiciona a execução HTTP além de <code>Elastic.Esql</code> via <code>Elastic.Transport</code>. Se sua aplicação só precisa do ES|QL e nenhuma das outras APIs do Elasticsearch, essa é a opção de dependência mínima.</p><p><a href="https://www.nuget.org/packages/Elastic.Clients.Elasticsearch"><strong><code>Elastic.Clients.Elasticsearch</code></strong></a> é o cliente completo do Elasticsearch .NET. Também se baseia em <code>Elastic.Esql</code> e expõe o provedor LINQ via espaço de nome <code>client.Esql</code>. Esse é o ponto de entrada recomendado para a maioria das aplicações.</p><p>Ambos os pacotes da camada de execução fornecem a própria implementação do <code>IEsqlQueryExecutor</code>, a interface estratégica que conecta tradução e transporte.</p><p>Todos os três pacotes são compatíveis com o Native AOT quando usados com um <code>JsonSerializerContext</code> gerado por fonte. Para as informações completas sobre o cliente, consulte a <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/source-serialization#native-aot">documentação do Native AOT</a>.</p><h2>Além do básico</h2><p>O exemplo acima abordou filtragem, classificação e paginação. O provedor aceita um conjunto mais amplo de operações.</p><h3>Agregações</h3><p><code>GroupBy</code>, combinado com funções agregadas em <code>Select</code>, traduz-se em ES|QL <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/stats-by"><code>STATS ... BY</code></a>:</p>var stats = client.Esql.Query&lt;Product, object&gt;(q =&gt; q
    .GroupBy(p =&gt; p.Brand)
    .Select(g =&gt; new
    {
        Brand = g.Key,
        Count = g.Count(),
        AvgPrice = g.Average(p =&gt; p.Price),
        MaxPrice = g.Max(p =&gt; p.Price)
    }));

// -&gt; FROM products | STATS COUNT(*), AVG(price_usd), MAX(price_usd) BY brand<h3>Projeções</h3><p><code>Select</code>, com tipos anônimos gera os comandos <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/eval"><code>EVAL</code></a>, <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/keep"><code>KEEP</code></a> e <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/rename"><code>RENAME</code></a>:</p>var query = client.Esql.CreateQuery&lt;Product&gt;()
    .Select(p =&gt; new { ProductName = p.Name, p.Price, p.InStock });

// -&gt; FROM products | KEEP name, price_usd, in_stock | RENAME name AS ProductName<h3>Biblioteca repleta de funções</h3><p>Mais de 80 funções ES|QL estão disponíveis via classe <code>EsqlFunctions</code>, cobrindo data/hora, string, matemática, IP, correspondência de padrões e pontuação. Métodos <code>Math.*</code> padrão e <code>string.*</code> também são traduzidos:</p>.Where(p =&gt; p.Name.Contains("Pro"))       // -&gt; WHERE name LIKE "*Pro*"
.Where(p =&gt; EsqlFunctions.CidrMatch(      // -&gt; WHERE CIDR_MATCH(ip, "10.0.0.0/8")
    p.IpAddress, "10.0.0.0/8"))<h3>PESQUISAR ENTRAR</h3><p>Consultas cruzadas de índice traduzem-se para ES|QL <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/lookup-join"><code>LOOKUP JOIN</code></a>:</p>var enriched = client.Esql.Query&lt;Product, object&gt;(q =&gt; q
    .LookupJoin&lt;Product, CategoryLookup, string, object&gt;(
        "category-lookup-index",
        product =&gt; product.Id,
        category =&gt; category.CategoryId,
        (product, category) =&gt; new { product.Name, category!.CategoryLabel }));<h3>Acesso direto ao ES|QL bruto</h3><p>Para recursos do ES|QL que ainda não são cobertos pelo provedor LINQ, você pode adicionar fragmentos brutos:</p>var results = client.Esql.Query&lt;Product&gt;(q =&gt; q
    .Where(p =&gt; p.InStock)
    .RawEsql("| EVAL discounted = price_usd * 0.9"));<h3>Consultas assíncronas do lado do servidor</h3><p>Para consultas de longa duração, envie-as para processamento em segundo plano no servidor:</p>await using var asyncQuery = await client.Esql.SubmitAsyncQueryAsync&lt;Product&gt;(
    q =&gt; q.Where(p =&gt; p.InStock),
    asyncQueryOptions: new EsqlAsyncQueryOptions
    {
        WaitForCompletionTimeout = TimeSpan.FromSeconds(5),
        KeepAlive = TimeSpan.FromMinutes(10)
    });

await asyncQuery.WaitForCompletionAsync();
await foreach (var product in asyncQuery.AsAsyncEnumerable())
    Console.WriteLine(product.Name);<p>Consultas assíncronas do lado do servidor são úteis principalmente em consultas analíticas de longa duração/processamento de grandes conjuntos de dados que podem exceder os tempos-limite típicos, ou em ambientes sensíveis a tempo-limite com balanceadores de carga, gateways de API ou proxies que impõem tempos-limite de HTTP rigorosos. Consultas assíncronas evitam quedas de conexão ao separar o envio da obtenção dos resultados.</p><h2>Para começar</h2><p>LINQ to ES|QL está disponível a partir de:</p><ul><li><p><strong>Elastic.Clients.Elasticsearch v9.3.4</strong> (9.x branch)</p></li><li><p><strong>Elastic.Clients.Elasticsearch v8.19.18</strong> (8.x branch)</p></li></ul><p>Instale do NuGet:</p><p><code>dotnet add package Elastic.Clients.Elasticsearch</code></p><p>Os pontos de entrada estão em <code>client.Esql</code>:</p><p>Método</p><p>Returns</p><p>Caso de uso</p><p>Query&lt;T&gt;(...)</p><p>IEnumerable&lt;T&gt;</p><p>Execução síncrona</p><p>QueryAsync&lt;T&gt;(...)</p><p>IAsyncEnumerable&lt;T&gt;</p><p>Streaming assíncrono</p><p>CreateQuery&lt;T&gt;()</p><p>IEsqlQueryable&lt;T&gt;</p><p>Composição avançada e inspeção</p><p>SubmitAsyncQueryAsync&lt;T&gt;(...)</p><p>EsqlAsyncQuery&lt;T&gt;</p><p>Consultas de longa duração no servidor</p><p><a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/linq-to-esql">Para obter a referência completa de recursos, incluindo opções de consulta, acesso a vários campos, objetos aninhados e tratamento de campos de vários valores, consulte a documentação do LINQ to ES|QL.</a></p><h2>Conclusão</h2><p>Do LINQ para ES|QL traz toda a expressividade do C# LINQ para a linguagem de consulta ES|QL do Elasticsearch, para que você escreva consultas componíveis e com tipagem forte sem precisar criar manualmente as strings de consulta. Com captura automática de parâmetros, materialização em streaming e arquitetura de pacotes em camadas que se adapta de traduções independentes ao cliente completo do Elasticsearch, ele se integra naturalmente a aplicações .NET de qualquer tamanho. Instale o cliente mais recente, direcione suas expressões LINQ para um índice e deixe o provedor cuidar do resto.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/linq-esql-c-elasticsearch-net-client</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/linq-esql-c-elasticsearch-net-client</guid>
    <category><![CDATA[ES|QL]]></category>
    <category><![CDATA[Banco de dados vetorial]]></category>
    <dc:creator><![CDATA[Florian Bernd,Martijn Laarman]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdfa35fbcbbf4959f/6a1705b9dc55de19a4e00d07/e54132e915217063e9ed0ec45059c6cfc38e31dd-1280x720.png" length="0" type="image/png"/>
    <pubDate>Wed, 01 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>