.netelasticsearchnestamazon-opensearchelasticsearch-nested

elasticsearch .net client complex query not working


I've got list of objects called selected which is this shape: List<{bool Selected, String Discriminator, List<int> Tags}>.

I need to say if document.VgnItm.Discriminator is one of selected[index].Discriminator, AND document.VgnItm.Tags has at least one tag from selected[index].Tags, then select that document.

This works when selected contains only one item:

for (var i = 0; i < selected.Count; i++)
{
    QueryContainer &= Query<VgnItmEstSearchDto>.Bool(boolean => boolean.Must(booleanMust =>
            booleanMust.Term(term => term.Field(termField => termField.VgnItm.Discriminator)
                .Value(selected[i].Discriminator)),
               booleanMust => booleanMust.Terms(terms => terms
                .Field(p => p.VgnItm.Tags)
                .Terms(selected[i].Tags)
            ))
    );
}

But when selected contains two or more items the QueryContainer &= means that the document.VgnItm.Discriminator must be selected[index1].Discriminator, and also selected[index2].Discriminator which is impossible. So it doesn't find any results.

What can I do here to make this work when selected contains multiple items? I can't just use QueryContainer |= because the QueryContainer has other clauses above. I tried wrapping Query<VgnItmEstSearchDto>.Bool(boolean => boolean.Must in a Bool.Should so it is a double Bool, but it had the same result.


Solution

  • I was able to do it by composing QueryContainers in the correct manner. As long as we compose like so: var compositeQueryContainer = childQueryA.QueryContainer &= childQueryB.QueryContainer, then having QueryContainer |= Query<VgnItmEstSearchDto>.Bool as I mentioned in my question, will not negate a previous query, it will combine them (AND them rather than OR them).

    So I can create the custom query exactly how it is in the question but use an |= (OR) inside my loop, which builds a larger query container from every loop cycle:

    public class ChildQueryB :
        BaseQuery<VgnItmEstSearchDto>
    {
        public ChildQueryB(
            string searchTerm,
            List<DiscriminatorTags> selectedDiscriminatorTags = null,
            bool shouldSearchNumberFields = false) :
            base(searchTerm, shouldSearchNumberFields)
        {
    
            for (var i = 0; i < selectedDiscriminatorTags.Count; i++)
            {
                QueryContainer |= Query<VgnItmEstSearchDto>.Bool(boolean => boolean.Must(booleanMust =>
                        booleanMust.Term(term => term.Field(termField => termField.VgnItm.Discriminator)
                            .Value(selectedDiscriminatorTags[i].Discriminator)),
                           booleanMust => booleanMust.Terms(terms => terms
                            .Field(p => p.VgnItm.Tags)
                            .Terms(selectedDiscriminatorTags[i].Tags)
                        ))
                );
            }
        }
    }
    

    Then compose it with another child query in a parent query using &=:

    public class CompositeQuery :
        BaseQuery<VgnItmEstSearchDto>
    {
        public CompositeQuery(
            string searchTerm,
            List<DiscriminatorTags> selectedDiscriminatorTags = null,
            bool shouldSearchNumberFields = false) :
            base(searchTerm, shouldSearchNumberFields)
        {
    
            
            var childQueryA = new ChildQueryB(searchTerm, true);
            var childQueryB = new ChildQueryA(searchTerm, selectedDiscriminatorTags, true);
            base.QueryContainer = childQueryA.QueryContainer &=
                childQuery.QueryContainer;
        }
    }
    

    It can then be used like:

    var compositeQuery = new CompositeQuery(searchTerm, discriminatorTags.Where(d => d.Selected == true).ToList(), true);
    var results = searchService.Search(compositeQuery, pageNumber, pageSize);