Table of Contents

Search: index and query with Elasticsearch

Index typed documents and query them with LINQ, translated to the Elasticsearch query DSL.

What you'll build

A host using SquidStd.Search.Elasticsearch: register the provider, mark a record as indexable, then index a document and query it back with a strongly-typed IQueryable<T>. The contracts live in SquidStd.Search.Abstractions, so your code stays decoupled from the Elasticsearch client.

Prerequisites

  • .NET 10 SDK

  • dotnet add package SquidStd.Search.Elasticsearch

  • A running Elasticsearch (only needed to actually execute queries):

    docker run -p 9200:9200 -e discovery.type=single-node -e xpack.security.enabled=false elasticsearch:8.6.1
    

Steps

1. Register the Elasticsearch provider

AddElasticsearch registers the client and ISearchService against your cluster endpoint.


bootstrap.ConfigureServices(
    container => container.RegisterCoreServices().AddElasticsearch(
        new()
        {
            Uri = new("http://localhost:9200")
        }
    )
);

2. Define an indexable document

Implement IIndexableEntity (supplying the document id) and tag the type with [SearchIndex("orders")].


/// <summary>An order document stored in the <c>orders</c> index.</summary>
[SearchIndex("orders")]
public sealed record Order(string Id, string Status, int Total) : IIndexableEntity
{
    /// <summary>Stable document id within the index.</summary>
    public string IndexId => Id;
}

3. Index and query

IndexAsync stores a document; Query<T>() returns a LINQ surface whose async terminals (ToListAsync, CountAsync, FirstOrDefaultAsync) execute against the cluster.


await search.IndexAsync(new Order("1", "open", 150), true);

var open = await search.Query<Order>()
                       .Where(o => o.Status == "open")
                       .ToListAsync();

Console.WriteLine($"found {open.Count} open order(s)");

Run it

dotnet run --project samples/SquidStd.Samples.Search

With Elasticsearch running it prints found 1 open order(s).

How it works

ISearchService.Query<T>() returns a constrained IQueryable<T> backed by a custom provider. The expression tree is translated to the Elasticsearch query DSL by ElasticExpressionTranslator; the async terminals and the .Match/.FullText markers come from SquidStd.Search.Elasticsearch.Linq. The target index is resolved from the [SearchIndex] attribute (with ${VAR} expansion) or the lowercased type name.

See also