Skip to content

Instantly share code, notes, and snippets.

@viviandeveloper
Last active October 20, 2023 10:33
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save viviandeveloper/1a632d8bfb8fa62e23c4679b086728ce to your computer and use it in GitHub Desktop.
Save viviandeveloper/1a632d8bfb8fa62e23c4679b086728ce to your computer and use it in GitHub Desktop.
Solution to the wretched "There is no method 'GetResults' on type 'Sitecore.ContentSearch.Linq.QueryableExtensions' that matches the specified arguments" error when unit testing search. Sitecore is tightly coupled to their own implemention of IQueryProvider which contains an 'Execute' method not found on standard .Net implementions.
namespace Testing
{
public class NewsController_LatestNews
{
[Theory]
[AutoData]
public void Should_be_news_items_ordered_by_date_descending(IEnumerable<NewsResultItem> newsResultItems)
{
var searchContext = Substitute.For<IProviderSearchContext>();
var queryable = new LuceneProviderQueryableStub(enumerable);
searchContext.GetQueryable().Returns(queryable);
var newsController = new NewsController(indexName => searchContext);
var actual = newsController.LatestNews(5).Model;
Assert.IsGreater(actual.First().Date, actual.Last().Date);
}
}
// Decorator that delegates querying to 'System.Linq.EnumerableQuery'
// and provides the missing execute method Sitecore requires.
public class LuceneProviderQueryableStub<TElement> : IOrderedQueryable<TElement>, IOrderedQueryable, IQueryProvider
{
private readonly EnumerableQuery<TElement> innerQueryable;
public Type ElementType { get { return ((IQueryable)innerQueryable).ElementType; } }
public Expression Expression { get { return ((IQueryable)innerQueryable).Expression; } }
public IQueryProvider Provider { get { return this; } }
public LuceneProviderQueryableStub(IEnumerable<TElement> enumerable)
{
innerQueryable = new EnumerableQuery<TElement>(enumerable);
}
public LuceneProviderQueryableStub(Expression expression)
{
innerQueryable = new EnumerableQuery<TElement>(expression);
}
public IQueryable CreateQuery(Expression expression)
{
return new LuceneProviderQueryableStub<TElement>((IEnumerable<TElement>)((IQueryProvider)innerQueryable).CreateQuery(expression));
}
public IQueryable<TElement1> CreateQuery<TElement1>(Expression expression)
{
return (IQueryable<TElement1>)new LuceneProviderQueryableStub<TElement>((IEnumerable<TElement>)((IQueryProvider)innerQueryable).CreateQuery(expression));
}
public object Execute(Expression expression)
{
throw new NotImplementedException();
}
public TResult Execute<TResult>(Expression expression)
{
var items = this.ToArray();
object results = new SearchResults<TElement>(items.Select(s => new SearchHit<TElement>(0, s)), 0);
return (TResult)results;
}
public IEnumerator<TElement> GetEnumerator()
{
return ((IEnumerable<TElement>)innerQueryable).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public class NewsController
{
private readonly Func<string, IProviderSearchContext> searchContextFactory;
public NewsController(Func<string, IProviderSearchContext> searchContextFactory)
{
this.searchContextFactory = searchContextFactory;
}
public ViewResult LatestNews(int count)
{
using (var searchContext = searchContextFactory("news_index"))
{
var newsResults = searchContext.GetQueryable<NewsResultItem>().OrderByDescending(x => x.Date).Take(count).GetResults();
return View("~/Views/LatestNews.cshtml", newsResults);
}
}
}
}
@moo2u2
Copy link

moo2u2 commented Jan 17, 2017

Love this code, thanks! If you need to work with the TotalSearchResults then line 60 needs to be
object results = new SearchResults<TElement>(items.Select(s => new SearchHit<TElement>(0, s)), items.Length);

@soen
Copy link

soen commented Jan 19, 2017

Thanks for sharing! I've got a small problem though, since I use the 'Filter()' method on my queryable, where I get the following error: There is no method 'Filter' on type 'Sitecore.ContentSearch.Linq.QueryableExtensions. Have you encountered this error?

@patrykbuzowicz
Copy link

here is a piece of code that transforms Filter to .NET Where calls

internal class FilterCallsReplacer: ExpressionVisitor
{
    private static readonly MethodInfo FilterMethod = typeof(QueryableExtensions)
        .GetMethod(nameof(QueryableExtensions.Filter), BindingFlags.Static | BindingFlags.Public);

    private static readonly MethodInfo WhereMethod = typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public)
        .First(method => method.Name == nameof(Queryable.Where));

    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        return IsFilterMethod(node)
            ? RewriteToWhere(node)
            : base.VisitMethodCall(node);
    }

    private static bool IsFilterMethod(MethodCallExpression node)
    {
        return node.Method.IsGenericMethod && node.Method.GetGenericMethodDefinition() == FilterMethod;
    }

    private Expression RewriteToWhere(MethodCallExpression node)
    {
        var arguments = node.Arguments.ToArray();
        var type = node.Method.GetGenericArguments().First();
        var whereMethod = WhereMethod.MakeGenericMethod(type);

        return Expression.Call(whereMethod, arguments);
    }
}

you can attach it to both CreateQuery methods, like

expression = new FilterCallsReplacer().Visit(expression);

@moo2u2
Copy link

moo2u2 commented Mar 26, 2018

If calling queryable.Count() before the expression is evaluated it throws an error that the result of the Execute method can't be cast to TResult.

Fix is to insert between line 59-60 an extra check to see if the result is expected to be an int:
if (typeof(TResult) == typeof(int)) return (TResult) (object)(items.Length);

@AnkitaBiswasMou
Copy link

what is enumerable here?
var queryable = new LuceneProviderQueryableStub(enumerable);

@jwsadler
Copy link

Do you have an example using FacetOn and FacetPivotOn ?

@viviandeveloper
Copy link
Author

what is enumerable here?
var queryable = new LuceneProviderQueryableStub(enumerable);

Sorry very late coming back to this one. Probably a typo, I think it was supposed to be the newsResultItems argument.

Also worth checking out is this guy's repo https://github.com/spinico/MethodRedirect

You can even redirect a static method e.g. Queryable.Where<T>(this IQueryable<T> queryable, Expression<Func<T, bool>> expression) to your own stub implementation, although it requires a bit of tinkering to get working.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment