3

Azure Search .Net SDK potentially does not return all requested results to a request. In this case it will return a ContinuationToken as part of the result (DocumentSearchResult).

From here:

If Azure Search can't include all results in a single response, the response returned will include a continuation token that can be passed to ContinueSearch to retrieve more results. See DocumentSearchResultBase.ContinuationToken for more information. Note that this method is not meant to help you implement paging of search results. You can implement paging using the Top and Skip parameters to the Search method.

As such, it is recommended that when a ContinuationToken is returned, a call is made to ContinueSearch to get the rest of the results.

What is the best/recommended way to combine two objects of Type DocumentSearchResult<T> (one having come from the original Search and the other from ContinueSearch) so that I can return all results together to the consumer?

Here's my first stab at it ("PerformSearch" is the method to be called that should return all results):

    private DocumentSearchResult<T> PerformSearch<T>(string searchText, SearchParameters searchParameters) where T : class
    {
        var searchIndexClient = GetSearchIndexClient<T>();
        var searchResults = searchIndexClient.Documents.Search<T>(searchText, searchParameters);
        if (searchResults.ContinuationToken != null)
        {
            ContinueSearch(searchResults, searchIndexClient, searchResults.ContinuationToken);
        }

        return searchResults;
    }

    private void ContinueSearch<T>(DocumentSearchResult<T> previousResults, SearchIndexClient searchIndexClient, SearchContinuationToken continuationToken) where T : class
    {
        var results = searchIndexClient.Documents.ContinueSearch<T>(continuationToken);
        previousResults.AddResults(results);
        if (results.ContinuationToken != null)
        {
            ContinueSearch(previousResults, searchIndexClient, results.ContinuationToken);
        }
    }

    public static void AddResults<T>(this DocumentSearchResult<T> first, DocumentSearchResult<T> second) where T : class
    {
        foreach (var searchResult in second.Results)
        {
            first.Results.Add(searchResult);
        }

        foreach (var facet in second.Facets)
        {
            first.Facets.Add(facet.Key, facet.Value);
        }
    }
richard
  • 12,263
  • 23
  • 95
  • 151

2 Answers2

1

You can combine the results as you have in your example, but there is no point in combining the facets. This is because facets are computed across the entire results of the query on each request, not just over the slice of results that you get back. If you make a series of ContinueSearch requests and the index isn't modified during that time, you should get back exactly the same facets each time. If your index is changing and you want the most up-to-date facet results, return the final one to your client.

In terms of what's the recommended way of doing this, I actually don't recommend it unless you use $top to limit the overall size of the results, or you're prepared to deal with very large result sets. Azure Search limits the size of a response for good reasons, and those reasons probably apply to you too, assuming you're also writing a service.

If you're writing a client, maybe you should try implementing an iterator over the results (IEnumerable<SearchResult<T>>), although you'd have to return the extra information (facets, document count) out-of-band somehow.

Bruce Johnston
  • 8,344
  • 3
  • 32
  • 42
  • Hi Bruce, yes I am using top. It's a requirement of my interface with the azure search. From the documentation, it appears that Azure Search's limits and continuation is arbitrary. They make no promises about the reasons or behavior, and recommend not to use it for paging. They only say "be ready to handle it". So in this case I make sure that I'm paging properly (with top and skip) and need to make sure I get back ALL possible results for that "page". That's what my question is driven by. – richard Nov 04 '16 at 21:50
-1

I don't think you need those loops. The facets property is a dictionary, which assuming no duplicates means you can combine like this:

 dicA.Concat(dicB).ToDictionary(kvp => kvp.Key, kvp => kvp.Value)
Sentinel
  • 3,582
  • 1
  • 30
  • 44
  • Thanks. But that's pretty much just a different way to combine any dictionary...but is the overall method I'm using correct? Combining them that way? Or is there some method that is built into these libraries that I've missed? – richard Nov 03 '16 at 09:49
  • Not that I am aware of. I suppose it depends what at the end of the day you want to achieve. You could make a library of extension methods that returns different items in different ways, or a class that operates over a list of DocumentSearchResult – Sentinel Nov 03 '16 at 09:56
  • Basically I want to get back the results to my consumer as if there was no continuation search needed. I don't want the consumer to have to worry about that. So to the consumer it should be as if all search results came back in one go. – richard Nov 03 '16 at 16:09
  • Yeah but what does your 'Consumer' want to receive? For example you could implement an IObservable with reactive extensions. Then they could LINQ over your stream and you wouldn't have to worry. The question is too vague. – Sentinel Nov 03 '16 at 20:20
  • It's not vague. The question is very clear - get the results back as one DocumentSearchResult that includes all results regardless of whether a continuation was conducted or not. Abstract that continuation away. – richard Nov 03 '16 at 20:29
  • No your question is this : "What is the best/recommended way to combine two objects..." Your above is not the best or recommended. I provided an improvement. Going further, I personally would not even bother. – Sentinel Nov 03 '16 at 20:31
  • @Richard DocumentSearchResult may not be the best way to model the results for your client, especially if you plan to serialize those results and send them over the wire. You should own your own persistence contracts; Ours may change in ways that break your app. – Bruce Johnston Nov 03 '16 at 20:55
  • @BruceJohnston right, I have an interface in between to abstract it away. DocumentSearchResult is only used internally inside my AzureSearch library, but I consider internal consumers still a consumer for the purposes of this question. – richard Nov 04 '16 at 21:47