v9.3.4 및 v8.19.18부터 Elasticsearch .NET 클라이언트에는 런타임에 C# LINQ 표현식을 Elasticsearch Query Language(ES|QL) 쿼리로 변환하는 Language Integrated Query (LINQ) 제공자가 포함되어 있습니다. ES|QL 스트링을 직접 작성하는 대신 Where, Select, OrderBy, GroupBy 및 기타 표준 연산자를 사용하여 쿼리를 작성합니다. 제공자는 변환, 매개변수화, 결과 역직렬화를 처리하며, 결과 세트 크기와 관계없이 메모리 사용량을 일정하게 유지하는 행별 스트리밍도 포함됩니다.
첫 번째 쿼리
먼저 Elasticsearch 인덱스에 맵핑되는 일반 CLR 객체(POCO)를 정의합니다. 속성 이름은 표준 System.Text.Json 속성(예: [JsonPropertyName]) 또는 구성된 JsonNamingPolicy을(를) 통해 ES|QL 열 이름으로 확인됩니다. 클라이언트의 나머지 부분에 적용되는 소스 직렬화 규칙이 여기에도 동일하게 적용됩니다.
유형이 지정되면 쿼리는 다음과 같습니다.
제공자는 이를 다음과 같은 ES|QL로 변환합니다.
참고할 만한 몇 가지 세부 사항은 다음과 같습니다.
- 속성 이름 확인:
p.Price은(는)[JsonPropertyName]속성 때문에price_usd이(가) 되고,p.Brand은(는) 기본 camelCase 명명 정책에 따라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>을(를) 구현하여 이러한 표현식 트리를 대상 언어로 변환할 수 있습니다. Entity Framework는 이를 사용하여 SQL을 출력합니다. LINQ to ES|QL 제공자는 이를 사용하여 ES|QL을 출력합니다.
위 쿼리의 표현식 트리는 다음과 같습니다.

예제 쿼리의 표현식 트리입니다.
Take 은(는)OrderByDescending 을(를) 감싸고, 이는 Where 을(를) 감싸고, 이는 From 을(를) 감싸고, 이는 EsqlQueryable<Product> 상수를 감싸는 식으로 트리가 안쪽에서 바깥쪽으로 중첩됩니다. Where 술어는 그 자체로 &&, >=, == 연산자에 대한 BinaryExpression 노드의 하위 트리이며, MemberExpression 은(는) 속성 액세스 및 minPrice 및 brand 변수에 대한 클로저 캡처를 위한 리프입니다. 이는 제공자가 최종 ES|QL을 생성하기 위해 거치는 데이터 구조입니다.
작동 원리 살펴보기: 변환 파이프라인
LINQ 표현식에서 쿼리 결과에 이르는 경로는 다음과 같이 6단계 파이프라인을 따릅니다.

변환 파이프라인 개요.
1. 표현식 트리 캡처
.Where(), .OrderBy(), .Take() 및 기타 연산자를 IQueryable<T>에 연결하면 표준 LINQ 인프라가 표현식 트리를 구축합니다. EsqlQueryable<T>은(는) IQueryable<T>을(를) 구현하고 EsqlQueryProvider에 위임합니다.
2. 번역
쿼리가 실행될 때(열거, ToList() 호출 또는 await foreach) 사용), EsqlExpressionVisitor은(는) 표현식 트리를 안쪽에서 바깥쪽으로 탐색합니다. 각 LINQ 메서드 호출을 전문 방문자에게 전달합니다.
| 방문자 | 번역합니다 | 안으로 |
|---|---|---|
| WhereClauseVisitor | .Where(predicate) | WHERE condition |
| SelectProjectionVisitor | .Select(선택기) | EVAL + KEEP + RENAME |
| GroupByVisitor | .GroupBy().Select() | STATS ... BY |
| 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 응답을 스트리밍합니다. 쿼리당 한 번씩 미리 계산되는 ColumnLayout 트리는 플랫 ES|QL 열 이름(예: address.street, address.city)을(를) 중첩된 POCO 속성에 맵핑합니다. 각 행은 T 인스턴스로 조립되어 IEnumerable<T> 또는 IAsyncEnumerable<T>을(를) 통해 한 번에 하나씩 제공됩니다.
계층 아키텍처
LINQ to ES|QL 기능은 다음과 같이 세 개의 패키지로 나뉩니다.

패키지 아키텍처.Elastic.Esql 은(는) 순수 변환 엔진입니다. HTTP 종속성이 전혀 없으며 표현식 방문자, 쿼리 모델, 포맷터 및 응답 판독기를 포함합니다. Elasticsearch 연결 없이 독립적으로 사용하여 ES|QL 쿼리를 구축하고 검사할 수 있어 테스트, 쿼리 로깅 또는 자체 실행 계층을 구축하는 데 유용합니다.
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와(과) 함께 사용할 경우 Native AOT와 호환됩니다. 전체 클라이언트에 대한 자세한 내용은 Native AOT 설명서를 확인하세요.
기본을 넘어서
위의 예에서는 필터링, 정렬 및 페이지 매김을 다뤘습니다. 공급자는 더 광범위한 작업을 지원합니다.
집계
GroupBySelect의 집계 함수와 결합하면 ES|QL STATS ... BY(으)로 변환됩니다.
프로젝션
Select익명 유형은 EVAL, KEEP, RENAME 명령을 생성합니다.
풍부한 함수 라이브러리
EsqlFunctions 클래스를 통해 날짜/시간, 스트링, 수학, IP, 패턴 매칭 및 스코어링을 포함한 80개 이상의 ES|QL 함수를 사용할 수 있습니다. 다음과 같이 표준 Math.* 및 string.* 메서드도 변환됩니다.
조회 조인
교차 인덱스 조회는 ES|QL LOOKUP JOIN(으)로 변환됩니다.
원시 ES|QL 이스케이프 해치
LINQ 제공자가 아직 지원하지 않는 ES|QL 기능의 경우 다음과 같이 원시 조각을 추가할 수 있습니다.
서버 측 비동기 쿼리
장기 실행 쿼리의 경우 다음과 같이 서버에서 백그라운드 처리를 위해 제출하세요.
서버 측 비동기 쿼리는 일반적인 시간 초과 임계값을 초과할 수 있는 장기 실행 분석 쿼리/대규모 데이터 세트 처리에 특히 유용합니다. 또한 엄격한 HTTP 시간 초과를 적용하는 로드 밸런서, API 게이트웨이 또는 프록시가 있는 시간 초과에 민감한 환경에서도 유용합니다. 비동기 쿼리는 제출과 결과 검색을 분리하여 연결 끊김을 방지합니다.
시작하기
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 에 있습니다.
| 메서드 | 반환 | 사용 사례 |
|---|---|---|
| Query<T>(...) | IEnumerable<T> | 동기식 실행 |
| QueryAsync<T>(...) | IAsyncEnumerable<T> | 비동기 스트리밍 |
| CreateQuery<T>() | IEsqlQueryable<T> | 고급 구성 및 검사 |
| SubmitAsyncQueryAsync<T>(...) | EsqlAsyncQuery<T> | 장기 실행 서버 측 쿼리 |
쿼리 옵션, 다중 필드 액세스, 중첩 객체, 다중 값 필드 처리 등 전체 기능에 대한 참조는 LINQ to ES|QL 설명서를 확인하세요.
결론
LINQ to ES|QL은 C# LINQ의 모든 표현력을 Elasticsearch의 ES|QL 쿼리 언어로 제공하여 쿼리 스트링을 직접 작성하지 않고도 강력한 형식의 조합 가능한 쿼리를 작성할 수 있도록 해줍니다. 자동 매개변수 캡처, 스트리밍 구체화, 그리고 독립 실행형 변환부터 전체 Elasticsearch 클라이언트까지 확장 가능한 계층형 패키지 아키텍처를 통해 모든 규모의 .NET 애플리케이션에 자연스럽게 통합됩니다. 최신 클라이언트를 설치하고 LINQ 표현식에서 인덱스를 지정하기만 하면 나머지는 제공자가 처리합니다.




