# 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](https://dev.ucommerce.net/readme/search-and-indexing/indexing/index-definitions).
{% endhint %}

## Facets

Facets can be used to filter based on preconfigured dimensions. First, follow the documentation on how to [create a facet](https://dev.ucommerce.net/readme/indexing/facets#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](https://dev.ucommerce.net/readme/search-and-indexing/indexing/facets) 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))
```
