-
-
Save viviandeveloper/1a632d8bfb8fa62e23c4679b086728ce to your computer and use it in GitHub Desktop.
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); | |
} | |
} | |
} | |
} |
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?
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);
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);
what is enumerable here?
var queryable = new LuceneProviderQueryableStub(enumerable);
Do you have an example using FacetOn and FacetPivotOn ?
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.
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);