Search Results for

    Show / Hide Table of Contents

    Using search

    Using the search features of PnP Core SDK you can issue queries and process the returned search results.

    In the remainder of this article you'll see a lot of context use: in this case this is a PnPContext which was obtained via the PnPContextFactory as explained in the overview article and show below:

    using (var context = await pnpContextFactory.CreateAsync("SiteToWorkWith"))
    {
        // See next chapter on how to use the PnPContext for working with files
    }
    

    Making a basic search request

    Let's start with making a simple search request by using one of the Search methods on IWeb. When making a search request you specify the search query and related options via a SearchOptions object as shown in below snippet. The key input for a search request is the actual search query (contenttypeid:"0x010100*" in below sample) and you can learn more about how to construct that query from here.

    The returned search result rows are in form of a Dictionary<string, object> with the search result property name as key and the search result property value as value.

    // Let's search for all lists using a particular content type and
    // for the found rows return the listed "select" properties
    SearchOptions searchOptions = new SearchOptions("contenttypeid:\"0x010100*\"")
    {
        TrimDuplicates = false,
        SelectProperties = new System.Collections.Generic.List<string>() { "Path", "Url", "Title", "ListId" },
    };
    
    var searchResult = await context.Web.SearchAsync(searchOptions);
    
    foreach (var result in searchResult.Rows)
    {
        if (result["Title"] == "abc")
        {
            // Do something
        }
    }
    

    Sorting the search results

    Often you want to also sort the search results on one or more properties and this is supported via the SortProperties attribute of the SearchOptions class. Checkout this documentation to learn more about the sort options offered by search.

    SearchOptions searchOptions = new SearchOptions("contenttypeid:\"0x010100*\"")
    {
        TrimDuplicates = false,
        SelectProperties = new System.Collections.Generic.List<string>() { "Path", "Url", "Title", "ListId" },
        // Define the properties to use for sorting the results, sorting on DocId a best practice
        // to increase search query performance
        SortProperties = new System.Collections.Generic.List<SortOption>()
        {
            new SortOption("DocId"),
            new SortOption("ModifiedBy", SortDirection.Ascending)
        },
    };
    
    var searchResult = await context.Web.SearchAsync(searchOptions);
    

    Getting refiners for the returned search results

    When making a search request you can also ask the search engine to refine the search results on one or more refiners. Refiners are managed properties that are marked as refinable in the search schema and when used you'll get next to the actual search results also a result set per refiner showing you how the returned search results are grouped based upon the refiner. Checkout this documentation to learn more about refiners. An example will provide more clarity: using below code we'll query all document libraries in the tenant and refine on contenttypeid. As a result you'll get a list of all document libraries in the search results and in the refinements you'll get a break down per contenttypeid, you'll see that for contenttypeid x there are y libraries using it, for contenttypeid Z there are n libraries using it, ...

    SearchOptions searchOptions = new SearchOptions("contentclass:STS_ListItem_DocumentLibrary")
    {
        TrimDuplicates = false,
        SelectProperties = new System.Collections.Generic.List<string>() { "Path", "Url", "Title", "ListId" },
        SortProperties = new System.Collections.Generic.List<SortOption>() { new SortOption("DocId") },
        RefineProperties = new System.Collections.Generic.List<string>() { "ContentTypeId" }
    };
    
    var searchResult = await context.Web.SearchAsync(searchOptions);
    
    // Process the search results
    foreach (var result in searchResult.Rows)
    {
        if (result["Title"] == "abc")
        {
            // Do something
        }
    }
    
    // Process the refiner results
    foreach (var refiner in searchResult.Refinements)
    {
        foreach (var refinementResult in refiner.Value)
        {
            // refinementResult.Value is a possible refinement value
            // refinementResult.Count will provide the number of counts for the refinement value
        }
    }
    

    Refining search results using refinement filters

    The following example shows how to create a search request by passing the first refinement option obtained from the previous example search result. Set the RefinementFilters property to a list of KQL queries, which can be built up using the Token from previous refinement results.

    // Retrieve the refinement
    var refinementResults = searchResult.Refinements["ContentTypeId"];
    var refinementToken = refinementResults[0].Token;
    
    SearchOptions searchOptions = new SearchOptions("contentclass:STS_ListItem_DocumentLibrary")
    {
        RefinementFilters = new System.Collections.Generic.List<string>() { $"ContentTypeId:{refinementToken}" }
    };
    
    // Issue the search query
    var searchResult = await context.Web.SearchAsync(searchOptions);
    
    

    Paging the search results

    By default search results are returned in pages of 500, something you can change via the RowsPerPage attribute of the SearchOptions class. When a search query returns you'll be informed of the total amount of search results there are via the TotalRows and TotalRowsIncludingDuplicates properties of the ISearchResult response. Using these properties in concert with the StartRow property of the SearchOptions class you retrieve all the search result pages. Below sample shows how this can be done.

    List<Dictionary<string, object>> searchResults = new List<Dictionary<string, object>>();
    
    bool paging = true;
    int startRow = 0;
    
    while (paging)
    {
        SearchOptions searchOptions = new SearchOptions("contenttypeid:\"0x010100*\"")
        {
            StartRow = startRow,
            TrimDuplicates = false,
            SelectProperties = new System.Collections.Generic.List<string>() { "Path", "Url", "Title", "ListId" },
            SortProperties = new System.Collections.Generic.List<SortOption>()
            {
                new SortOption("DocId"),
                new SortOption("ModifiedBy", SortDirection.Ascending)
            },
        };
    
        // Issue the search query
        var searchResult = await context.Web.SearchAsync(searchOptions);
    
        // Add the returned page of results to our search results list
        searchResults.AddRange(searchResult.Rows);
    
        // If we're not done yet update the start row and issue a query to retrieve the next page
        if (searchResults.Count < searchResult.TotalRows)
        {
            startRow = searchResults.Count;
        }
        else
        {
            // We're done!
            paging = false;
        }
    }
    
    // Process the total search result set
    foreach (var result in searchResults)
    {
        if (result["Title"] == "abc")
        {
            // Do something
        }
    }
    

    Batching search queries

    PnP Core SDK also allows you to batch multiple search queries and send them in one operation to the server, this can be done by using one of the SearchBatch methods.

    var batch = context.NewBatch();
    Dictionary<Guid, IBatchSingleResult<ISearchResult>> batchResults =
        new Dictionary<Guid, IBatchSingleResult<ISearchResult>>();
    
    List<Guid> uniqueListIds = new List<Guid>();
    // Imagine the uniqueListIds contains a series of list id's that you want to issue a search query for
    
    foreach (var listId in uniqueListIds)
    {
        // Issue a search query with a refinement on `contenttypeid`, we don't need the
        // result rows, so `RowLimit` is set to 0
        var request = await context.Web.SearchBatchAsync(batch, new SearchOptions($"listid:{listId}")
        {
            RowLimit = 0,
            SortProperties = new List<SortOption>() { new SortOption("DocId") },
            RefineProperties = new List<string> { "contenttypeid" },
        });
        // Track the search query batch result objects
        batchResults.Add(listId, request);
    }
    
    // Execute the batch
    await context.ExecuteAsync(batch);
    
    // Process the search results
    foreach (var batchResult in batchResults)
    {
        // Check the IsAvailable attribute to ensure the search request was executed
        if (batchResult.Value.IsAvailable)
        {
            // The Result property is of type ISearchResult and can be used to process the search results
            if (batchResult.Value.Result.Refinements.Count > 0 &&
                batchResult.Value.Result.Refinements.ContainsKey("contenttypeid"))
            {
                foreach (var refinementResult in batchResult.Value.Result.Refinements["contenttypeid"])
                {
                    // Process the contenttypeid refiner results
                }
            }
        }
    }
    

    Re-indexing webs and lists

    When you make changes to the search schema (e.g. updated managed properties) then you'll need to re-index the relevant content, which can be done at list level or web level.

    Back to top PnP Core SDK
    Generated by DocFX with Material UI
    spacer