0

When performing content search queries, you can make use of the WithinRadius, OrderByDistance and OrderByDistanceDescending methods of the Sitecore.ContentSearch.Linq.QueryableExtensions class to get items within a certain radius of a location and order them. I am able to build a predicate as part of a call to Where and follow it up with a call to WithinRadius successfully, returning the results I expect.

However, I have a use case where I provide filters that can return items from an index based on whether it has an specific ID, is beneath a parent with a specific ID, OR it falls within a specific radius of a location. For example, I might want to retrieve all items within a specific radius, but I also want to include an item with a specific ID in the search results, even though it may not fall in within the radius of a location. This would require an OR clause, but the WithinRadius only works as part of an AND clause (source.Where(predicate).WithinRadius(...)).

I have the option to perform two separate searches - one for within a radius, and another for items with a specific ID - before calling Union on the two, but this will not allow me to call Distinct or allow for any form of pagination without getting the results for each query first, which can slow down the query and include duplicate results.

What I would ideally like is a way to build up a lambda expression of type Expression<Func<TIndexedItem, bool>> which contains the predicate to search within a radius, which can then be used as part of an OR clause in a predicate builder rather than as part of an AND clause. For example, my use case could implement the following class:

public class ItemPredicateBuilder<TIndexedItem>
{
    public Guid? ID { get; set; }

    public Coordinate Coordinate { get; set; }

    public double Radius { get; set; }

    public Expression<Func<TIndexedItem, bool>> BuildIdEqualsPredicate()
    {
        if (!this.ID.HasValue) return PredicateBuilder.True<TIndexedItem>();

        var sitecoreID = new ID(this.ID.Value);
        return item => item.ID == sitecoreID;
    }

    public Expression<Func<TIndexedItem, bool>> BuildWithinRadiusPredicate()
    {
        // The following is pseudo-code, and implementation of this method is what my question alludes to requiring.
        if (this.Coordinate == null || this.Radius <= 0) return PredicateBuilder.True<TIndexedItem>();

        return WithinRadius(item => item.Location, this.Coordinate, this.Radius);
    }

    public Expression<Func<TIndexedItem, bool>>  BuildPredicate()
    {
        PredicateBuilder.Or(this.BuildIdPredicate(), this.BuildWithinRadiusPredicate());
    }

    public SearchResults<TIndexedItem> Query()
    {
        using (var context = this.GetContext())
        {
            return context.GetQueryable<TIndexedItem>().Where(this.BuildPredicate()).GetResults();
        }
    }

    public IProviderSearchContext GetSearchContext()
    {
        ...
    }
}

I have attempted to decompile the QueryableExtensions class (using dotPeek), but can't make much sense of the implementation of the WithinRadius method. Copying the implementation locally in my own QueryableExtensions class so that I can debug and step through to see how it works also leads to nowhere as exceptions are thrown in this instance, and I am struggling to create any sort of lambda expression to perform this spatial search within the method in the above code block.

My question is, how could I go about implementing the BuildWithinRadius method of the above class, and is there a similar expression that can be build for use in the OrderBy Linq method rather than explicitly calling OrderByDistance?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.