Search code examples
linqgenericssitecoresitecore7

Generic Linq with specific where/order by clauses


I want to create a generic method that allows me to search a Sitecore 7 index using Linq to Sitecore (.Net 4.5).

This will be used for various searches such as:

  • Get top 5 latest news pages
  • Get the last 20 event pages
  • Generic site search
  • Etc, etc, etc

I can create a very generic search method, which works for all types of page. It provides a generic template predicate for the Where clause that would be applicable for all types of search.

However, for the searches above I also need to add specific predicates for the Where clause, and specific expressions for Order By etc. The intention would be to create subclasses for each type of search, which would implement these specifics.

I've condensed some code which is shown below. In this I try to add specific functionality for a new page search.

All page classes derive from "Base".

public virtual ReadOnlyCollection<T> Search<T>() where T : Base, new()
    {
        List<T> results = new List<T>();

        using (IProviderSearchContext context = ContentSearchManager.GetIndex("sitecore_web_index").CreateSearchContext())
        {
            Expression<Func<T, bool>> outerPredicate = PredicateBuilder.True<T>();

            // Create a predicate for the template id.
            Expression<Func<T, bool>> templatePredicate = PredicateBuilder.False<T>();
            templatePredicate = templatePredicate.Or(baseItem => (baseItem.TemplateIdFromIndex.Equals("8b1fc00c76314d32b8e1bce93dd41ccd")));

            // Create a predicate for a news page search.
            Expression<Func<NewsPageBase, bool>> datePredicate = PredicateBuilder.False<NewsPageBase>();
            datePredicate = datePredicate.And(newsPage => newsPage.ArticleDate < DateTime.Now);

            // 1. outerPredicate = outerPredicate.And(datePredicate);

            // 2. IQueryable<T> searchQuery = context.GetQueryable<T>().Where(outerPredicate).OrderByDescending(newsPage => newsPage.ArticleDate).Take(5);
            IQueryable<T> searchQuery = context.GetQueryable<T>().Where(outerPredicate);

            results = searchQuery.ToList();
        }

        return new ReadOnlyCollection<T>(results);
    }

This code complies and runs.

However, if I uncomment the line marked as [1], a compiler error me from Anding the generic template predicate with the specific news page predicate.

The error is "The type arguments for method 'Sitecore.ContentSearch.Linq.Utilities.PredicateBuilder.And(System.Linq.Expressions.Expression>, System.Linq.Expressions.Expression>)' cannot be inferred from the usage".

It's a similar error if I uncomment the line marked as [2].

How do I create a generic method which has the specific functionality for each type of search?


Solution

  • Modifying your code as follows achieves what you need..

    public virtual ReadOnlyCollection<T> Search<T>() where T : Base, new()
        {
            List<T> results = new List<T>();
    
            using (IProviderSearchContext context = ContentSearchManager.GetIndex("sitecore_web_index").CreateSearchContext())
            {
                Expression<Func<T, bool>> outerPredicate = PredicateBuilder.True<T>();
    
                // Create a predicate for a news page search.
                Expression<Func<NewsPageBase, bool>> datePredicate = PredicateBuilder.True<NewsPageBase>();
                datePredicate = datePredicate.And(newsPage => newsPage.ArticleDate < DateTime.Now);
    
                //outerPredicate = outerPredicate.And((Expression<Func<T,bool>>)(object)datePredicate);
                outerPredicate = outerPredicate.And((Expression<Func<T, bool>>)(object)datePredicate);
    
                // 2. IQueryable<T> searchQuery = context.GetQueryable<T>().Where(outerPredicate).OrderByDescending(newsPage => newsPage.ArticleDate).Take(5);
                IQueryable<T> searchQuery = context.GetQueryable<T>().Where(outerPredicate);
    
                results = searchQuery.ToList();
            }
    
            return new ReadOnlyCollection<T>(results);
        }
    

    The changes I made were:

    1. Removed the unused templatePredicate - I'm not sure if you had intended to use this or had forgotten to remove it when condensing your original code.
    2. Create datePredicate using PredicateBuilder.True instead of PredicateBuilder.False - this is needed for use in .And() otherwise no results will be returned.
    3. I (un)box datePredicate for use within outerPredicate.And(). The compiler complains if you try to cast datePredicate to Expression<Func<T, bool>> but casting it to object first solves this.