Custom column analyzers for Dataverse Search - Power Apps

Optimize Dataverse Search with Custom Column Analyzers in Power Apps

Dataverse search is a powerful tool within Power Apps that helps users find the data they need quickly. By default, it leverages Azure AI Search capabilities, using both index and search analyzers to deliver relevant results. However, sometimes the default analyzers might not be sufficient, especially when dealing with special characters, unusual data formats, or specific linguistic requirements. This article delves into how you can tailor your search results by implementing custom column analyzers for Dataverse search, and how it enhances the precision and relevance of your search results.

Understanding the Need for Custom Analyzers

Sometimes Dataverse Search doesn't return an exact match because the data in the searched column isn't easily understood, or because certain characters are automatically removed or ignored based on the default analyzer used by Dataverse Search service

Here are some examples where Dataverse search might not return the exact results you're looking for:

Desired Actual
AB-84(q)(1)(c)orAB-84(1)(1)(-c) Exact match Unwanted matches: Returns records with AB, (1), or (c) in a column resulting in multiple records that aren't relevant.
2.2.3.1 Exact match Unwanted matches: Returns records with 2.2, 2.3.1, .2 resulting in multiple records that aren't relevant
PG-11.1 Exact match Unwanted matches: Returns records with PG, -11, -11.1 resulting in multiple records that aren't relevant
"%mn" +"ABC-123" Exact match for: record has mnABC-123 Unwanted matches: record with mnhas a record with ABC-123 but doesn't include mn
"Inspector of brakes" Exact match Unwanted match: Inspector of boilers

In such cases, custom analyzers can provide the necessary instructions to Dataverse Search to accurately match keywords and phrases with the expected data, ensuring more precise and relevant search outcomes.

Leveraging Azure AI Search Analyzers

Dataverse search relies on the robust Azure AI Search analyzers. So, understanding how these analyzers work is crucial for refining your search results. It's highly recommended to familiarize yourself with Analyzers for text processing in Azure AI Search to gain a deeper insight on managing searches.

Built-in Analyzers

By default, Dataverse search uses the Microsoft language analyzer based on the Dataverse organization's base language. If a specific Microsoft analyzer isn't available, it defaults to the Lucene Analyzer. These analyzers are automatically applied to all string or memo columns marked as searchable.

You can also use Built-in analyzers if the Azure AI Search built-in analyzers for a specific column works for you. You can also use available language analyzers, especially when dealing with data in languages different from your Dataverse base language.

To implement a built-in analyzer, create a row in the SearchAttributeSettings table, and set the Name, entityname, and attributename appropriately. Then configure the settings to point to a built-in search analyzer, like {"analyzer": "keyword"}, or a language analyzer, such as { "analyzer": "it.microsoft"}.

Setting Up a Custom Analyzer for a Column

To apply a different analyzer for a Dataverse table column, you need to configure the SearchAttributeSettings table. By default, this table is empty.

Step-by-Step Guide to Configure Power Apps

  1. Access Power Apps: Navigate to Power Apps.
  2. Select Environment: Ensure you’re in the correct environment by checking the environment name in the header.
  3. Open Tables: Choose "Tables" from the left navigation pane and select "All tables".
  4. Find SearchAttributeSettings: Search for "searchattributesettings" in the top right corner.
  5. Edit Columns: Open the SearchAttributeSettings table and ensure the "Name", "attributename", "entityname", and "settings" columns are visible. Add them if necessary by selecting “+18 more” next to the Name column.
  6. Save Changes: After selecting the columns, save your changes.

Manually Editing the SearchAttributeSettings Table

Column Description
Name A descriptive name to help you identify the custom analyzer.
entityname The logical name of the table containing the column you want to configure.
attributename The logical name of the column for which the analyzer is used.
settings A JSON string that specifies your custom analyzer. For example: { "analyzer": "name_analyzer"} or {"indexanalyzer": "name_analyzer", "searchanalyzer": "name_analyzer"}.

Important: The combination of entityname and attributename must be unique to avoid errors. To prevent unintentional duplication, consider adding an alternate key to the SearchAttributeSettings table, specifying these two columns in the key by using Power Apps.

Implementing Changes with Code

Alternatively, you can programmatically update the SearchAttributeSettings table to avoid manual errors and ensure consistency.

Using SDK for .NET

The following C# code demonstrates how to create or replace a SearchAttributeSettings record:

/// <summary>
/// Creates or replaces a SearchAttributeSettings record
/// </summary>
/// <param name="service">The authenticated IOrganizationService instance</param>
/// <param name="entityName">
/// The name of the entity for which the search attribute settings record is created.
/// </param>
/// <param name="attributeName">
/// The logical name of the column for which the search attribute settings record is created.
/// </param>
/// <param name="settings">The settings for the search attribute.</param>
/// <param name="overwriteExisting">Verify that the intent is to replace any existing record</param>
static void SetSearchAttributeSettings(IOrganizationService service, string entityName, string attributeName, string settings, bool overwriteExisting = false)
{
    try
    {
        #region Check whether record exists
        Entity existingRecord = null;
        QueryExpression query = new("searchattributesettings")
        {
            ColumnSet = new ColumnSet(
                "searchattributesettingsid",
                "entityname",
                "attributename",
                "settings"),
            Criteria = new FilterExpression(LogicalOperator.And)
            {
                Conditions = {
                    new ConditionExpression("entityname", ConditionOperator.Equal, entityName),
                    new ConditionExpression("attributename", ConditionOperator.Equal, attributeName)
                }
            }
        };

        // Retrieve any matching records
        EntityCollection entityCollection = service.RetrieveMultiple(query);

        if (entityCollection.Entities.Count > 1)
        {
            string message = "More than one record is found in searchattributesettings ";
            message += $"with entityname {entityName} and attributename {attributeName}.";
            throw new Exception(message);
        }

        if (entityCollection.Entities.Count == 1)
        {
            existingRecord = entityCollection.Entities[0];
        }
        #endregion Check whether record exists

        if (existingRecord != null)
        {
            string currentSettings =
. . .