从 v9.3.4 和 v8.19.18 开始,Elasticsearch .NET 客户端包含一个语言集成查询 (LINQ) 提供程序,可在运行时将 C# LINQ 表达式转换为 Elasticsearch 查询语言 (ES|QL) 查询。您可以使用Where、Select、OrderBy、GroupBy 和其他标准操作符来编写查询,而无需手工编写 ES|QL 字符串。提供程序负责转换、参数化和结果反序列化,包括按行流式传输,无论结果集大小如何,都能保持稳定的内存使用量。
您的第一个查询
首先定义一个映射到 Elasticsearch 索引的普通旧 CLR 对象 (POCO)。属性名称通过标准System.Text.Json 属性(如[JsonPropertyName])或配置的JsonNamingPolicy 解析为 ES|QL 列名。适用于客户端其他部分的源序列化规则在这里也同样适用。
类型设置完成后,查询语句如下所示:
该提供程序将此转换为以下 ES|QL:
需要注意的一些细节:
- 属性名称解析:由于
[JsonPropertyName]属性,p.Price变成了price_usd,根据默认 camelCase 命名策略,p.Brand变成brand。 - 参数捕获:C# 变量
minPrice和brand被捕获为命名参数 (?minPrice,?brand)。它们与 JSON 有效负载中的查询字符串分开发送,这样可以防止注入,并实现服务器端查询计划缓存。 - 流式传输:
QueryAsync<T>返回IAsyncEnumerable<T>。从 Elasticsearch 返回数据时,数据会逐行具体化。
您还可以在不执行的情况下检查生成的查询及其参数:
这如何运作?快速回顾一下 LINQ
使 LINQ 提供程序成为可能的机制是 IEnumerable<T> 和 IQueryable<T> 之间的区别。
在 IEnumerable<T> 上调用 .Where(p => p.Price > 100) 时,lambda 会编译为 Func<Product, bool>,即一个由运行时在进程内执行的常规委托。这就是 LINQ-to-Objects。
当您在IQueryable<T> 上调用相同的方法时,C# 编译器会将 lambda 封装在Expression<Func<Product, bool>> 中。这是一种数据结构,表示代码的结构,而不是代码的可执行形式。在运行时,该表达式树可被检查、分析,并转换为另一种语言。
IQueryProvider 接口是扩展点。任何提供程序均可通过实现 CreateQuery<T> 和 Execute<T>,将这些表达式树转换为目标语言。实体框架就是利用此机制生成 SQL 语句。LINQ to ES|QL 提供程序使用它来生成 ES|QL 查询。
上述查询的表达式树如下所示:

示例查询的表达式树。
此表达式树由内而外嵌套:Take 包裹着 OrderByDescending,它又包裹着 Where,而后者再包裹着 From,而最内层是根节点 EsqlQueryable<Product> 常量。对于 &&、>= 和 == 这几种操作符而言,Where 谓词本身是一个由 BinaryExpression 个节点构成的子树,其中包含 MemberExpression 个叶子节点,这些叶子节点用于属性访问,以及对 minPrice 和 brand 变量的闭包捕获。提供程序会遍历这一数据结构,从而生成最终的 ES|QL 查询。
深入了解:转换管道
从 LINQ 表达式到查询结果的路径遵循六阶段管道:

转换管道概述。
1. 表达式树捕获
当在一个 IQueryable<T> 对象上串联使用 .Where()、.OrderBy()、.Take() 及其他操作符时,标准的 LINQ 基础架构会构建一个表达式树。EsqlQueryable<T> 实现了 IQueryable<T> 接口,并将处理委托给 EsqlQueryProvider。
2. 翻译
当查询被执行 (通过枚举、调用 ToList(),或使用 await foreach) 时),EsqlExpressionVisitor自内而外遍历表达式树。它会将每个 LINQ 方法调用分派给一个专门的访问器进行处理:
| 访客 | 翻译 | 进入 |
|---|---|---|
| WhereClauseVisitor | .Where(predicate) | WHERE 条件 |
| SelectProjectionVisitor | .Select(selector) | 评估 + 保留 + 重命名 |
| GroupByVisitor | .GroupBy().Select() | 统计信息 ... 依据 |
| OrderByVisitor | .OrderBy() / .ThenBy() | SORT 字段 [ASC\|DESC] |
| EsqlFunctionTranslator | EsqlFunctions.*、Math.*、字符串方法 | 80+ ES|QL 函数 |
在翻译过程中,表达式中引用的 C# 变量被捕获为命名参数。
3. 查询模型
访问器不会直接生成字符串。相反,它们会产生 QueryCommand 对象,一个不可变的中间表征。一个 FromCommand、一个 WhereCommand、一个 SortCommand 和一个 LimitCommand,各代表一条 ES|QL 处理命令。这些数据被收集到EsqlQuery 模型中。

查询模型和命令模式。
该中间模型与表达式树和输出格式均解耦。它可以被检查、拦截(通过 IEsqlQueryInterceptor)或在格式化前进行修改。
4. 格式化
EsqlFormatter 依次访问每个QueryCommand ,并生成最终的 ES|QL 字符串。每条命令占一行,通过 ES|QL 中用于串联处理命令的管道 (|) 运算符分隔。若标识符包含特殊字符,系统会自动用反引号进行转义处理。
5. 执行
格式化后的 ES|QL 查询字符串及捕获的参数会以 JSON 数据载荷的形式发送至 Elasticsearch 的 /_query 终端。而 IEsqlQueryExecutor 接口则对传输层进行了抽象封装,这正是分层包架构发挥作用的关键环节。
6. 实现
EsqlResponseReader 流式传输JSON响应,但不会将整个结果集缓冲到内存中。以流式方式传输 JSON 响应数据,无需将整个结果集缓存至内存。针对每次查询预先计算生成的 ColumnLayout 树结构,会将扁平化的 ES|QL 列名(如 address.street、address.city)映射到嵌套的 POCO 属性。每行数据会被组装为 T 实例,并通过 IEnumerable<T> 或 IAsyncEnumerable<T> 逐个返回。
分层架构
LINQ to ES|QL 功能分为三个软件包:

软件包架构。Elastic.Esql 是纯转换引擎。该组件完全不依赖 HTTP 协议栈,集成了表达式访问器、查询模型、格式化器及响应解析器等核心模块。您可独立使用它来构建和检查 ES|QL 查询(无需连接 Elasticsearch),这在测试验证、查询日志记录或自定义执行层开发等场景中极具实用价值。翻译要点解析:
Elastic.Clients.Esql 是一款轻量级的独立 ES|QL 客户端。该组件通过 Elastic.Transport 在 Elastic.Esql 之上扩展了 HTTP 协议执行能力。如果您的应用程序仅需使用 ES|QL 而无需其他 Elasticsearch API,此方案可实现最小化依赖集成。
Elastic.Clients.Elasticsearch 是完整的 Elasticsearch.NET 客户端。它还建立在Elastic.Esql 的基础上,并通过client.Esql 命名空间公开 LINQ 提供程序接口。这是大多数应用程序的推荐入口点。
两个执行层组件包均提供了针对 IEsqlQueryExecutor 接口的独立实现。该策略接口作为转换与传输层的桥梁。
当与源码生成的 JsonSerializerContext 配合使用时,这三个组件包均支持原生 AOT 编译。如需完整客户端集成方案,请参阅原生 AOT 文档。
不只使用基础功能
上面的例子涵盖了筛选、排序和分页。该提供程序支持更广泛的操作范围。
聚合
GroupBy结合 Select 中的聚合函数,转换为 ES|QL STATS ... BY:
投影
Select,使用匿名类型生成 EVAL、KEEP 和 RENAME 命令:
丰富的函数库
通过 EsqlFunctions 类,可以使用超过 80 个 ES|QL 函数,涵盖日期/时间、字符串、数学、IP、模式匹配和评分。标准 Math.* 和 string.* 方法也已转换:
查找连接
跨索引查找转换为 ES|QL LOOKUP JOIN:
原生 ES|QL 直通接口
对于 LINQ 提供程序尚未涵盖的 ES|QL 功能,您可以添加原始片段:
服务器端异步查询
对于长时间运行的查询,可将其提交给服务器进行后台处理:
服务器端异步查询对于长时间运行的分析型查询/大规模数据集处理尤其有用,这类操作可能会超出常规的超时阈值;在存在负载均衡器、API 网关或代理(这些组件会强制执行严格的 HTTP 超时设置)的超时敏感环境中,异步查询同样优势显著。异步查询通过将查询提交与结果获取解耦,避免了连接中断的情况。
开始使用
LINQ to ES|QL 自以下版本起可用:
- Elastic.Clients.Elasticsearch v9.3.4(9.x 分支)
- Elastic.Clients.Elasticsearch v8.19.18(8.x 分支)
从 NuGet 安装:
dotnet add package Elastic.Clients.Elasticsearch
入口点位于client.Esql上:
| 方法 | 返回值 | 用例 |
|---|---|---|
| 查询<T>(...) | IEnumerable<T> | 同步执行 |
| QueryAsync<T>(...) | IAsyncEnumerable<T> | 异步流式处理 |
| CreateQuery<T>() | IEsqlQueryable<T> | 高级结构分析和检测 |
| SubmitAsyncQueryAsync<T>(...) | EsqlAsyncQuery<T> | 长时间运行的服务器端查询 |
有关完整的功能参考,包括查询选项、多字段访问、嵌套对象和多值字段处理,请参阅LINQ to ES|QL 文档。
结论
LINQ 转 ES|QL 将 C# LINQ 的强大表达能力引入到 Elasticsearch 的 ES|QL 查询语言中,让您无需手工编写查询字符串,就能生成强类型、可组合的查询。它具备自动参数捕获、流式物化功能,还拥有分层式的软件包架构,既能满足独立转换需求,也能适配完整的 Elasticsearch 客户端,可自然融入任意规模的 .NET 应用程序。安装最新客户端,将 LINQ 表达式指向索引,剩下的就交给该提供程序来处理。




