<?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/jp/search-labs/author/martijn-laarman</link>
    </image>
    <link>https://www.elastic.co/jp/search-labs/author/martijn-laarman</link>
    <atom:link href="https://www.elastic.co/jp/search-labs/rss/author/martijn-laarman.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[jp]]></language>
    <lastBuildDate>Tue, 29 Sep 2026 01:24:14 GMT</lastBuildDate>
  <item>
    <title><![CDATA[LINQ to Elasticsearch ES|QL：C#を記述してElasticsearchをクエリ]]></title>
    <description><![CDATA[Elasticsearch .NETクライアントに新しく追加されたLINQ to Elasticsearch ES|QLプロバイダをご紹介します。C#コードを自動的にES|QLクエリに変換できます。]]></description>
    <content:encoded><![CDATA[<p><strong>v9.3.4</strong>および<strong>v8.19.18</strong>以降のElasticsearch .NETクライアントには、実行時にC# LINQ式を<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql.html">Elasticsearchクエリ言語（ES|QL）クエリに変換する</a><a href="https://learn.microsoft.com/en-us/dotnet/csharp/linq/">Language Integrated</a> Query（LINQ）プロバイダーが含まれています。ES|QL文字列を手作業で記述する代わりに、 <code>Where</code>、 <code>Select</code>、 <code>OrderBy</code>、 <code>GroupBy</code>などの標準演算子を使用してクエリを構成します。このプロバイダーは、結果セットのサイズに関係なくメモリ使用量を一定に保つ行ごとのストリーミングを含め、変換、パラメータ化、結果の逆シリアル化を処理します。</p><h2>最初のクエリ</h2><p>まず、Elasticsearchインデックスにマップする普通のCLRオブジェクト（POCO）を定義します。プロパティ名は、標準的な<code>System.Text.Json</code>属性（<code>[JsonPropertyName]</code>など）または設定された<code>JsonNamingPolicy</code>を通じてES|QL列名に解決されます。クライアントの他の部分に適用される<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/source-serialization">ソースシリアル化</a>ルールは、ここでも同様に適用されます。</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>型を指定すると、クエリは次のようになります。</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>プロバイダーはこれを次のES|QLに変換します。</p><p>いくつか注意すべき点があります。</p><ul><li><p><strong>プロパティ名の解決：</strong> <code>p.Price</code>は<code>[JsonPropertyName]</code> 属性のため<code>price_usd</code>になり、<code>p.Brand</code>はデフォルトのcamelCase命名規則に従って <code>brand</code>になります。</p></li><li><p><strong>パラメーターのキャプチャ：</strong>C#変数 <code>minPrice</code>と<code>brand</code>は、名前付きパラメーター（<code>?minPrice</code>、<code>?brand</code>）としてキャプチャされます。これらはJSONペイロード内のクエリ文字列とは別に送信されるため、インジェクション攻撃を防ぎ、サーバー側のクエリプランのキャッシュを可能にします。</p></li><li><p><strong>ストリーミング：</strong><code>QueryAsync&lt;T&gt;</code>は<code>IAsyncEnumerable&lt;T&gt;</code>を返します。Elasticsearchからデータが到着すると、行は1つずつマテリアライズされます。</p></li></ul><p>また、実行せずに生成されたクエリとそのパラメーターを検査することもできます。</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>これはどのように機能するのでしょうか？LINQ の簡単なおさらい</h2><p>LINQプロバイダーを可能にするメカニズムは、<code>IEnumerable&lt;T&gt;</code>と<code>IQueryable&lt;T&gt;</code>の区別にあります。</p><p><code>.Where(p =&gt; p.Price &gt; 100)</code> を <code>IEnumerable&lt;T&gt;</code> 上で呼び出すと、ラムダは <code>Func&lt;Product, bool&gt;</code> にコンパイルされます。これは、ランタイムがインプロセスで実行する通常のデリゲートです。これはLINQ-to-Objectsです。</p><p>同じメソッドを <code>IQueryable&lt;T&gt;</code> で呼び出すと、C#コンパイラはラムダを <code>Expression&lt;Func&lt;Product, bool&gt;&gt;</code> でラップします。これは実行可能な形式ではなく、コードの<em>構造</em>を表すデータ構造です。式ツリーは実行時に検査、分析、および別の言語への変換を行うことができます。</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><code>IQueryProvider</code>インターフェースは拡張ポイントです。どのプロバイダーでも、これらの式ツリーをターゲット言語に変換するために <code>CreateQuery&lt;T&gt;</code> と <code>Execute&lt;T&gt;</code> を実装できます。Entity FrameworkはSQLを発行するためにこれを使用します。LINQからES|QLへのプロバイダーはこれをES|QLの生成に使用します。</p><p>上記のクエリの式ツリーは次のようになります。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt521838e8b9c36649/6a1705b1839dfa5f40dcfdfe/f864cd18a390831f8d28503a29b5835efb1842f7-1000x720.png" alt="例のクエリに対する式ツリー。" /><p><em>例のクエリに対する式ツリー。</em></p><p>ツリーは内側から外側にネストされています。<code>Take</code>が <code>OrderByDescending</code>をラップし、これが<code>Where</code>をラップし、これが<code>From</code>, をルート定数<code>EsqlQueryable&lt;Product&gt;</code> をラップします。<code>Where</code>述語自体が<code>BinaryExpression</code>ノードのサブツリーであり、<code>&amp;&amp;</code>、<code>&gt;=</code>、および<code>==</code>演算子に対して<code>MemberExpression</code>リーフがプロパティアクセス用、<code>minPrice</code>および<code>brand</code>変数用のクロージャキャプチャ用に存在します。これは、プロバイダーが最終的なES|QLを生成するために使用するデータ構造です。</p><h2>内部構造：変換パイプライン</h2><p>LINQ式からクエリ結果までの経路は、6段階のパイプラインをたどります。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt930670a505dd61ea/6a1705b3b339d58a54769ecf/2a2c772b63d720f61fc9a28b2f85668fa2db8d38-1999x1036.png" alt="データ変換パイプラインの概要。" /><p><em>データ変換パイプラインの概要。</em></p><h3>1. 式ツリーのキャプチャ</h3><p><code>.Where()</code>、<code>.OrderBy()</code>、<code>.Take()</code>などの演算子を<code>IQueryable&lt;T&gt;</code> に連鎖させると、標準のLINQインフラストラクチャーが式ツリーを構築します。<code>EsqlQueryable&lt;T&gt;</code> は<code>IQueryable&lt;T&gt;</code> を実装し、<code>EsqlQueryProvider</code> に委譲します。</p><h3>2. 変換</h3><p>クエリが実行されると（列挙、 <code>ToList()</code>の呼び出し、または<code>await foreach)</code>使用によって）、 <code>EsqlExpressionVisitor</code>は式ツリーを内側から外側へと走査します。各LINQメソッド呼び出しを専門のビジターに送信します。</p><p>ビジター</p><p>翻訳します</p><p>対象</p><p>WhereClauseVisitor</p><p>.Where(predicate)</p><p>WHERE 条件</p><p>SelectProjectionVisitor</p><p>.Select(selector)</p><p>EVAL + KEEP + RENAME</p><p>訪問者別にグループ化</p><p>.GroupBy().Select()</p><p>STATS ... BY</p><p>OrderByVisitor</p><p>.OrderBy() / .ThenBy()</p><p>SORTフィールド [ASC\|DESC]</p><p>EsqlFunctionTranslator</p><p>EsqlFunctions.*、Math.*、文字列メソッド</p><p>80+ ES|QL関数</p><p>翻訳中、式で参照されるC#変数は名前付きパラメーターとしてキャプチャされます。</p><h3>3. クエリモデル</h3><p>ビジターは直接文字列を生成しません。代わりに、<code>QueryCommand</code>オブジェクト、すなわち不変の中間表現を生成します。<code>FromCommand</code>、<code>WhereCommand</code>、<code>SortCommand</code>、および<code>LimitCommand</code>の各々が、1つのES|QL処理コマンドを表しています。これらは<code>EsqlQuery</code>モデルに集められます。</p><p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt788c9936976f2f62/6a1705b50e2e4910da419ff0/2adc349b6cf655b96b7b3e826a134e8a17fe42fd-1999x1036.png" alt="クエリモデルとコマンドパターン。" /><p><em>クエリモデルとコマンドパターン。</em></p><p>この中間モデルは、式ツリーと出力形式の両方から切り離されています。フォーマット前に検査、傍受（<code>IEsqlQueryInterceptor</code>経由）、または修正が可能です。</p><h3>4. フォーマット</h3><p><code>EsqlFormatter</code> 各<code>QueryCommand</code>を順番に訪問し、最終的なES|QL文字列を生成します。各コマンドは1行になり、ES|QLが処理コマンドを連鎖させるために使用するパイプ (|) 演算子で区切られます。特殊文字を含む識別子は自動的にバッククォートでエスケープされます。</p><h3>5. 実行</h3><p>フォーマットされたES|QL文字列とキャプチャされたパラメーターは、JSONペイロードとしてElasticsearchの<code>/_query</code>エンドポイントに送信されます。<code>IEsqlQueryExecutor</code>インターフェースはトランスポートレイヤーを抽象化し、ここで階層型パッケージアーキテクチャが登場します。</p><h3>6. マテリアライズ</h3><p><code>EsqlResponseReader</code> JSON応答をストリーム化し、結果セット全体をバッファリングせずに処理します。<code>ColumnLayout</code>ツリーは、1クエリにつき1回事前に計算され、フラットなES|QL列名（<code>address.street</code>、<code>address.city</code>など）をネストされたPOCOプロパティにマップします。各行は<code>T</code>インスタンスに組み立てられ、 <code>IEnumerable&lt;T&gt;</code> または <code>IAsyncEnumerable&lt;T&gt;</code>によって1行ずつ生成されます。</p><h2>レイヤーアーキテクチャ</h2><p>LINQ to ES|QL機能は、以下の3つのパッケージに分かれています。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt662bd0dd8861b6b6/6a1705b7a929cf7086ae08a2/41b8aae860ecdc2480edcb1c1d4cc9b03cfb78c9-1999x1036.png" alt="パッケージアーキテクチャー。" /><p><em>パッケージアーキテクチャー。</em><a href="https://www.nuget.org/packages/Elastic.Esql"><strong><code>Elastic.Esql</code></strong></a> は純粋な変換エンジンです。HTTPへの依存関係は一切なく、式ビジター、クエリモデル、フォーマッター、レスポンスリーダーが含まれています。スタンドアロンで使用すると、Elasticsearch接続がなくてもES|QLクエリを構築および検査できます。これは、テスト、クエリロギング、または独自の実行レイヤーの構築に役立ちます。</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> は軽量なスタンドアロンのES|QLクライアントです。<code>Elastic.Transport</code>を経由して<code>Elastic.Esql</code>上にHTTP実行を追加します。もしアプリケーションが他のElasticsearch APIではなく、ES|QLのみを必要とする場合、これが最小限の依存関係オプションです。</p><p><a href="https://www.nuget.org/packages/Elastic.Clients.Elasticsearch"><strong><code>Elastic.Clients.Elasticsearch</code></strong></a> は完全なElasticsearch .NETクライアントです。また、<code>Elastic.Esql</code> を基盤とし、<code>client.Esql</code>名前空間を通じてLINQプロバイダーを公開します。これはほとんどのアプリケーションで推奨されるエントリーポイントです。</p><p>どちらの実行層パッケージも、変換と転送をつなぐ戦略インターフェースである<code>IEsqlQueryExecutor</code>の独自の実装を提供します。</p><p>これら3つのパッケージはすべて、ソース生成の<code>JsonSerializerContext</code>と併用する場合、ネイティブAOTと互換性があります。完全なクライアントについては、<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/source-serialization#native-aot">Native AOTのドキュメント</a>をご覧ください。</p><h2>基本を超えて</h2><p>上記の例では、フィルタリング、ソート、ページネーションについて説明しています。このプロバイダーはより幅広い操作をサポートしています。</p><h3>アグリゲーション</h3><p><code>GroupBy</code><code>Select</code>の集約関数と組み合わせると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>予測</h3><p><code>Select</code>匿名型を持つと、 <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>、 <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>豊富な関数ライブラリ</h3><p>80以上のES|QL関数が <code>EsqlFunctions</code>クラスを通じて利用可能で、日付/時間、文字列、数学、IP、パターンマッチング、スコアリングをカバーしています。標準的な<code>Math.*</code>および<code>string.*</code>メソッドも変換されています。</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>ルックアップ結合</h3><p>クロスインデックス検索は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>未加工のES|QLエスケープハッチ</h3><p>LINQプロバイダーでまだサポートされていないES|QL機能については、生のフラグメントを追加できます。</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>サーバー側の非同期クエリ</h3><p>実行時間の長いクエリについては、サーバー上でバックグラウンド処理を行うように設定します。</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>サーバー側の非同期クエリは、通常のタイムアウトしきい値を超える可能性のある長時間実行される分析クエリや大規模データセットの処理、あるいはロードバランサー、APIゲートウェイ、プロキシなど、厳格なHTTPタイムアウトを強制するタイムアウトに敏感な環境で特に役立ちます。非同期クエリは、結果の取得から提出を切り離すことで接続切断を回避します。</p><h2>はじめに</h2><p>LINQ to ES|QLは次のバージョンから利用可能です。</p><ul><li><p><strong>Elastic.Clients.Elasticsearch v9.3.4</strong> (9.x ブランチ)</p></li><li><p><strong>Elastic.Clients.Elasticsearch v8.19.18</strong>（8.xブランチ）</p></li></ul><p>NuGetからのインストール：</p><p><code>dotnet add package Elastic.Clients.Elasticsearch</code></p><p>エントリーポイントは<code>client.Esql</code>にあります。</p><p>メソッド</p><p>戻り値</p><p>ユースケース</p><p>Query&lt;T&gt;(...)</p><p>IEnumerable&lt;T&gt;</p><p>同期実行</p><p>QueryAsync&lt;T&gt;(...)</p><p>IAsyncEnumerable&lt;T&gt;</p><p>非同期ストリーミング</p><p>CreateQuery&lt;T&gt;()</p><p>IEsqlQueryable&lt;T&gt;</p><p>高度な構成と検査</p><p>SubmitAsyncQueryAsync&lt;T&gt;(...)</p><p>EsqlAsyncQuery&lt;T&gt;</p><p>長時間実行されるサーバー側クエリ</p><p>クエリオプション、複数フィールドへのアクセス、ネストされたオブジェクト、複数値フィールドの処理など、機能の詳細については<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/linq-to-esql">LINQ to ES|QLのドキュメントを</a>参照してください。</p><h2>まとめ</h2><p>LINQ to ES|QLは、C# LINQの完全な表現力をElasticsearchのES|QLクエリ言語にもたらし、クエリ文字列を手作業で作成することなく、厳密に型付けされた構成可能なクエリを書くことができます。自動パラメーターキャプチャ、ストリーミングマテリアライゼーション、スタンドアロン変換から完全なElasticsearchクライアントまで拡張できる階層型パッケージアーキテクチャーにより、あらゆる規模の.NETアプリケーションに自然に適合します。最新のクライアントをインストールし、LINQ式をインデックスに向け、残りはプロバイダーに任せましょう。</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[Vector Database]]></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>