To be able to search on a field in the Sitecore SearchAPI you need to add that field to a model. So you could create a new class derived from SearchResultItem
and add your tags field in:
public class MySearchResultItem: SearchResultItem
{
[TypeConverter(typeof (IndexFieldEnumerableConverter))]
[IndexField("tags")]
public IEnumerable<ID> Tags { get; set; }
}
I'm here assuming that your Tags
field is a list type field that stores the Guids of the tags. The IndexField
attribute tells Sitecore what the field name is for this property. The TypeConverter
field tells the API how to convert the value stored in the index to the C# type.
Now you have that you can use it in your Linq query:
using (var context = ContentSearchManager.GetIndex(indexName).CreateSearchContext())
{
//Filters related articles
var relatedSearchQuery = context.GetQueryable<MySearchResultItem>()
.Where(item => item.ItemId != currentArticle.Id.ToID() &&
item.Tags.Contains(currentArticle.TagId))
Here I am assuming a single tag on the current article.
If you need to compare a list with a list it gets a bit more complicated. In that case you need to create a search extension that will build that query.
Here is the code. I have included the comments in the code to explain what is going on.
public static class SearchExtensions
{
public static IQueryable<TSource> ContainsOr<TSource, TKey>(this IQueryable<TSource> queryable, Expression<Func<TSource, TKey>> keySelector, IEnumerable values) where TKey : IEnumerable
{
return Contains(queryable, keySelector, values, true);
}
public static IQueryable<TSource> ContainsAnd<TSource, TKey>(this IQueryable<TSource> queryable, Expression<Func<TSource, TKey>> keySelector, IEnumerable values) where TKey : IEnumerable
{
return Contains(queryable, keySelector, values, false);
}
public static IQueryable<TSource> Contains<TSource, TKey>(this IQueryable<TSource> queryable, Expression<Func<TSource, TKey>> keySelector, IEnumerable values, bool orOperator) where TKey : IEnumerable
{
const string methodName = "Contains";
// Ensure the body of the selector is a MemberExpression
if (!(keySelector.Body is MemberExpression))
{
throw new InvalidOperationException("Expression must be a member expression");
}
var typeOfTSource = typeof(TSource);
var typeOfTKey = typeof(TKey);
// x
var parameter = Expression.Parameter(typeOfTSource);
// Create the enumerable of constant expressions based off of the values
var constants = values.Cast<object>().Select(id => Expression.Constant(id));
IEnumerable<MethodCallExpression> expressions = Enumerable.Empty<MethodCallExpression>();
/*
* Create separate MethodCallExpression objects for each constant expression created
*
* Each expression will effectively be like running the following;
* x => x.MyIdListField.Contains(AnId)
*
* Check to see if we can find a method on TKey type which matches the method we want to run.
* We do this because not all types use the static IEnumerable extension e.g. the String class
* has it's own implementation of .Contains.
*
* If we can't find a matching method then we try to run the extension method found in Enumerable
*/
if (typeOfTKey.GetMethods().Any(m => m.Name.Equals(methodName)))
{
var method = typeOfTKey.GenericTypeArguments.Any() ? typeOfTKey.GetMethod(methodName, typeOfTKey.GenericTypeArguments) : typeOfTKey.GetMethod(methodName);
/*
* instance -> this would be property we want to run the expession on e.g.
* IQueryable<MyPocoTemplate>.Where(x => x.MyIdListField)
* so keySelector.Body will contain the "x.MyIdListField" which is what we want to run
* each constant expression against
* method -> the method to run against the instance e.g. "x.MyIdListField.Contains(...)"
* arguments ->
* constant -> this is the constant expression (value) to be passed to the method
*/
expressions = constants.Select(constant => Expression.Call(keySelector.Body, method, constant));
}
else
{
/*
* type -> we need to specify the type which contains the method we want to run
* methodName -> in this instance we need to specify the Contains method
* typeArguments -> the type parameter from TKey
* e.g. if we're passing through IEnumerable<Guid> then this will pass through the Guid type
* this is because we're effectively running IEnumerable<Guid>.Contains(Guid guid) for each
* guid in our values object
* arguments ->
* keySelector.Body -> this would be property we want to run the expession on e.g.
* IQueryable<MyPocoTemplate>.Where(x => x.MyIdListField)
* so keySelector.Body will contain the "x.MyIdListField" which is what we want to run
* each constant expression against
* constant -> this is the constant expression (value) to be passed to the method
*/
var typeArgs = typeOfTKey.IsArray ? new[] { typeOfTKey.GetElementType() } : typeOfTKey.GenericTypeArguments;
expressions = constants.Select(constant => Expression.Call(typeof(Enumerable), methodName, typeArgs, keySelector.Body, constant));
}
/*
* Combine all the expressions into one expression so you would end with something like;
*
* x => x.MyIdListField.Contains(AnId) OR x.MyIdListField.Contains(AnId) OR x.MyIdListField.Contains(AnId)
*/
var aggregateExpressions = expressions.Select(expression => (Expression)expression).Aggregate((x, y) => orOperator ? Expression.OrElse(x, y) : Expression.AndAlso(x, y));
// Create the Lambda expression which can be passed to the .Where
var lambda = Expression.Lambda<Func<TSource, bool>>(aggregateExpressions, parameter);
return queryable.Where(lambda);
}
}
This will then allow you to do a .ContainsAnd
or a .ContainsOr
with your lists. ContainsAnd
means that all entries in the list must be present in the returned item. ContainsOr
returns items where it contains any of the ID's in the list.
So your query would become:
using (var context = ContentSearchManager.GetIndex(indexName).CreateSearchContext())
{
//Filters related articles
var relatedSearchQuery = context.GetQueryable<MySearchResultItem>()
.Where(item => item.ItemId != currentArticle.Id.ToID())
.ContainsAnd(item => item.Tags, currentArticle.Tags)
Where currentArticle.Tags
is an already populated list of ID's.
SearchExtensions code taken from Fortis Search Extensions
Tags
field to theSearchResultItem
class?