# Searching

## Indexes

Ucommerce has five built-in indexes: Store, Product, Catalog, Category, and PriceGroup.

To query an index, you need to take a dependency on `IIndex<T>` where `T` is one of the [Ucommerce search models](#search-models).<br>

```csharp
public class ProductController : Controller
{
    private readonly IIndex<ProductSearchModel> _productIndex;

    public ProductController(IIndex<ProductSearchModel> productIndex)
    {
        _productIndex = productIndex;
    }
}
```

The `IIndex` interface allows you to query the index with a LINQ-like fluid syntax.\
To get started, you get the `ISearch<Model>` object that you will do your queries on from the index.

```csharp
ISearch<ProductSearchModel> query = _productIndex.AsSearchable(new CultureInfo("da-DK"));
```

{% hint style="warning" %}
Be aware that the interface is LINQ-like but not LINQ. This means the interface does not support certain things. See [Query caveats](#query-caveats) for more details.
{% endhint %}

## Filtering

You can use the `Where()` method to filter the results in the index down to only the objects you are interested in. See [facets](#facets) below for filtering on preconfigured dimensions.

The simplest filter is to match on equality.

```csharp
var SKUQuery = query.Where(x => x.Sku == "Some SKU");
```

### Match

The Match class is for more advanced matching like Full Text, Literal, Fuzzy, and Wildcard.&#x20;

```csharp
// Full Text
var fullTextFilter = query.Where(x => x.DisplayName == Match.FullText("SearchTerm"));
// Fuzzy
var fuzzyFilter = query.Where(x => x.DisplayName == Match.Fuzzy("SearchTerm", 2));
// Literal
var literalFilter = query.Where(x => x.DisplayName == Match.Literal("SearchTerm"));
// Wildcard with case-insensitive searching, default is case-sensitive search
var wildcardFilter = query.Where(x => x.DisplayName == Match.Wildcard("*searchTerm*", true));
```

### Range

To query in a range, you can use the `Range(int from, int to)` method on the Match class.

```csharp
var rangeQuery = query
                .Where(x => x.PricesInclTax["pricegroup"] == Match.Range(100, 200));
```

## Sorting

You can sort the results with the `Order` and `OrderByDescending` methods.

```csharp
// Order By
var orderedSKUQuery = query.Where(x => x.Sku == "Some SKU")
                .OrderBy(x => x.DisplayName);
// Order By Descending                
var descendingSKUQuery = query.Where(x => x.Sku == "Some SKU")
                .OrderByDescending(x => x.DisplayName);
```

## Paging

Paging can be done using the Skip and Take methods.

```csharp
// The 4th to 6th result
var pagedQuery = query.Skip(3).Take(3);
```

## Results

Like with LINQ, the execution of the query is deferred until you finish the query definition and get the results by calling one of the following methods.

```csharp
ResultSet<ProductSearchModel> result = await query.ToResultSet(token);
         
FacetResultSet<ProductSearchModel> resultWithFacets = await query.ToFacets(token: token);

long count = await query.Count(token);

ProductSearchModel firstProduct = await query.First(token);
ProductSearchModel? firstOrDefaultProduct = await query.FirstOrDefault(token);

ProductSearchModel singleProduct = await query.Single(token);
ProductSearchModel? singleOrDefaultProduct = await query.SingleOrDefault(token);

IImmutableList<NewClass> selectResult = await query.Select<NewClass>(
                            x => new NewClass
                            {
                                // Map properties
                            },
                            token);
```

{% hint style="warning" %}
You should call these methods last to avoid unexpected behavior and keep the query on a search engine level instead of in memory. This also avoids the use of excessive resources on your web server.&#x20;
{% endhint %}

{% hint style="info" %}
The method `ToSuggestions` mentioned in [suggestions](#suggestions) also triggers the query execution.
{% endhint %}

### Total Count

If you need a total count for a query, it is available on the query result as `TotalCount`. The `TotalCount` property gives you the total number of objects in the index.\
To get the number of objects the query returns, use the `Count` property on the `Results` property.

```csharp
var totalCount = result.TotalCount;
var querycount = result.Results.Count;
```

## Suggestions

To give suggestions to the user when they are typing in a search field, you can use the `Suggestions()` search method.

```csharp
// SuggestionResultSet<ProductSearchModel>
var suggestionsResult = await _productIndex.AsSearchable(new CultureInfo("da-DK")
                                .ToSuggestions(
                                    "DisplayName", //Property to base suggestions on
                                    "se",          //Search Term
                                    false,         //Fuzzy?
                                    token);
```

The Fuzzy parameter controls whether or not the suggestions should be fuzzy, which takes spelling errors into account. This will impact performance, so keep that in mind.

{% hint style="info" %}
To get suggestions on a field you need to have added `Suggestable()` to the [Index Definition](/readme/search-and-indexing/indexing/index-definitions.md).
{% endhint %}

## Facets

Facets can be used to filter based on preconfigured dimensions. First, follow the documentation on how to [create a facet](/readme/search-and-indexing/indexing/facets.md#creating-a-facet). After facets are configured and the index created, the facets can be accessed using the `ToFacets()` search method. This method can also be told to return the results of the query to avoid multiple calls to Elasticsearch.

```csharp
var resultWithFacets = await query.ToFacets(false, token);
var facets = resultWithFacets.Facets;
foreach(var facet in facets)
{
    facet.DisplayName //Pretty name of the field name in the index, presented to the customer.
    facet.Name //Name of the field in the index that contains the possible values based on the current search.
    facet.TotalCount //Total Count of documents that has a field with any value based on the search.
    foreach(var val in facet.FacetValues)
    {
        val.DisplayName //The DisplayName of the facet value, Will differ from Value if the facet is multilingual or enum, e.g. "5 coupons".
        val.Value //The individual term as found in the Index, e.g. "5".
        val.Count //Count of documents matching this value based on an existing search.
    }
}
```

Note: The overall facet should be used for creating a header in the filter page while the individual facet values should become actual filters, e.g. checkboxes or sliders. See [facets](/readme/search-and-indexing/indexing/facets.md) for a visual example.

## Search Models

| Entity     | Search Model          |
| ---------- | --------------------- |
| Product    | ProductSearchModel    |
| Store      | StoreSearchModel      |
| Catalog    | CatalogSearchModel    |
| Category   | CategorySearchModel   |
| PriceGroup | PriceGroupSearchModel |

## Query Caveats

Although the fluid query syntax is LINQ-like, it isn't fully LINQ-compatible.

### Known Limitations

#### Contains

❌ Using `Contains()` on a search model collection property of complex objects does not work:

```csharp
ISearch<ProductSearchModel> query = _productIndex
    .AsSearchable(new CultureInfo("da-DK"))
    .Where(product => product.CategoryProductRelations.Contains(someRelation));
```

✅ Using `Contains()` on a search model collection property of simple types works fine:

```csharp
ISearch<ProductSearchModel> query = _productIndex
    .AsSearchable(new CultureInfo("da-DK"))
    .Where(product => product.CategoryIds.Contains(categoryGuid));
```

✅ Using `Contains()` on an in-memory collection checking for an index model property works fine:

```csharp
ISearch<ProductSearchModel> query = _productIndex
    .AsSearchable(new CultureInfo("da-DK"))
    .Where(product => productGuids.Contains(product.Guid))
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://dev.ucommerce.net/readme/search-and-indexing/searching.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
