3
votes

I am trying to implement elasticsearch into my webshop but having some troubles on using filters. The filtering is done dynamically.

Example:

I start with showing all the products that are indexed. So no filter is applied. Visitors can choose their own filters like: color, size, brand, type, category, ....

But I don't now how to build the search result with elasticsearch and NEST.

This is my solution without filtering:

var query = ElasticClient.Search<Product>(s => s
            .From(from)
            .Size(size)
        );

I also have another question on indexing a collection<> or list<>. I had to use JsonIgnore on those collections. Could I index those as well?

This is my class:

/// <summary>
/// Represents a product
/// </summary>
public partial class Product    {

    private ICollection<ProductCategory> _productCategories;
    private ICollection<ProductManufacturer> _productManufacturers;
    private ICollection<ProductPicture> _productPictures;


    /// <summary>
    /// Gets or sets the name
    /// </summary>
    public virtual string Name { get; set; }

    /// <summary>
    /// Gets or sets the short description
    /// </summary>
    public virtual string ShortDescription { get; set; }


    /// <summary>
    /// Gets or sets a value indicating whether the entity is published
    /// </summary>
    public virtual bool Published { get; set; }

    /// <summary>
    /// Gets or sets a value indicating whether the entity has been deleted
    /// </summary>
    public virtual bool Deleted { get; set; }

    /// <summary>
    /// Gets or sets the date and time of product creation
    /// </summary>
    public virtual DateTime CreatedOnUtc { get; set; }

    /// <summary>
    /// Gets or sets the date and time of product update
    /// </summary>
    public virtual DateTime UpdatedOnUtc { get; set; }



    /// <summary>
    /// Gets or sets the collection of ProductCategory
    /// </summary>
    [JsonIgnore] /* added - wesley */
    public virtual ICollection<ProductCategory> ProductCategories
    {
        get { return _productCategories ?? (_productCategories = new List<ProductCategory>()); }
        protected set { _productCategories = value; }
    }

    /// <summary>
    /// Gets or sets the collection of ProductManufacturer
    /// </summary>
    [JsonIgnore] /* added - wesley */
    public virtual ICollection<ProductManufacturer> ProductManufacturers
    {
        get { return _productManufacturers ?? (_productManufacturers = new List<ProductManufacturer>()); }
        protected set { _productManufacturers = value; }
    }

    /// <summary>
    /// Gets or sets the collection of ProductPicture
    /// </summary>
    [JsonIgnore] /* added - wesley */
    public virtual ICollection<ProductPicture> ProductPictures
    {
        get { return _productPictures ?? (_productPictures = new List<ProductPicture>()); }
        protected set { _productPictures = value; }
    }


}

Is there someone who can help me?

1

1 Answers

8
votes

Be sure to read the whole documentation on writing queries here: http://nest.azurewebsites.net/nest/writing-queries.html

What follows is a pasted excerpt from there.

Conditionless queries

Writing complex boolean queries is one thing but more often then not you'll want to make decisions on how to query based on user input.

public class UserInput
{
    public string Name { get; set; }
    public string FirstName { get; set; }
    public int? LOC { get; set; }
}

and then

.Query(q=> {
    QueryDescriptor<ElasticSearch> query = null;
    if (!string.IsNullOrEmpty(userInput.Name))
        query &= q.Term(p=>p.Name, userInput.Name);
    if (!string.IsNullOrEmpty(userInput.FirstName))
        query &= q
            .Term("followers.firstName", userInput.FirstName);
    if (userInput.LOC.HasValue)
        query &= q.Range(r=>r.OnField(p=>p.Loc).From(userInput.Loc.Value))
    return query;
})

This again turns tedious and verbose rather quickly too. Therefor nest allows you to write the previous query as:

.Query(q=>
    q.Term(p=>p.Name, userInput.Name);
    && q.Term("followers.firstName", userInput.FirstName)
    && q.Range(r=>r.OnField(p=>p.Loc).From(userInput.Loc))
)

If any of the queries would result in an empty query they won't be sent to elasticsearch.

So if all the terms are null (or empty string) on userInput except userInput.Loc it wouldn't even wrap the range query in a boolean query but just issue a plain range query.

If all of them empty it will result in a match_all query.

This conditionless behavior is turned on by default but can be turned of like so:

 var result = client.Search<ElasticSearchProject>(s=>s
    .From(0)
    .Size(10)
    .Strict() //disable conditionlessqueries by default
    ///EXAMPLE HERE
);

However queries themselves can opt back in or out.

.Query(q=>
    q.Strict().Term(p=>p.Name, userInput.Name);
    && q.Term("followers.firstName", userInput.FirstName)
    && q.Strict(false).Range(r=>r.OnField(p=>p.Loc).From(userInput.Loc))
)

In this example if userInput.Name is null or empty it will result in a DslException. The range query will use conditionless logic no matter if the SearchDescriptor uses .Strict() or not.

Also good to note is that conditionless query logic propagates:

q.Strict().Term(p=>p.Name, userInput.Name);
&& q.Term("followers.firstName", userInput.FirstName)
&& q.Filtered(fq => fq
    .Query(qff => 
        qff.Terms(p => p.Country, userInput.Countries)
        && qff.Terms(p => p.Loc, userInput.Loc)
    )
)

If both userInput.Countries and userInput.Loc are null or empty the entire filtered query will be not be issued.