Synthesis
Synthesis copied to clipboard
ContainsOr with an ItemReferenceListField doesn't work
Do you want to request a feature or report a bug? Bug
What is the current behavior? When attempting to use the "ContainsOr" extension method with an ItemReferenceListField fieldtype an exception is thrown:
public IQueryable<SearchResultItem> ShapeQuery1(IProviderSearchContext context, IQueryable<SearchResultItem> query, IBaseSearchPageConfigurationItem config)
{
var cfg = config as IPersonListingItem;
var q = context.GetQueryable<IBasePersonItem>();
if (cfg.PersonTypeFilter.HasValue)
// where k.PersonType is an IItemReferenceListField field
q = q.ContainsOr(k => k.PersonType, cfg.PersonTypeFilter.TargetIds.ToArray());
return q.Cast<SearchResultItem>().AsQueryable();
}
The exception is occurs in the ContentSearchQueryExtensions.cs class line 103:
System.InvalidOperationException: 'No method 'Contains' on type 'System.Linq.Enumerable' is compatible with the supplied arguments.'
From what I can tell, this occurs because the initial attempt to resolve the 'Contains' method on IItemReferenceListField fails to find it (line 70). Even though IItemReferenceListField has the base interface ICollection<ID>, it does not find it because it is not explicitly defined on IItemReferenceListField.
In order to resolve this issue, I came up with 2 solutions.
- The simple hack was to just add the 'Contains' method to the IItemReferenceListField interface using the 'new' keyword to hide the underlying 'Contains' method from ICollection<ID>
public interface IItemReferenceListField : ICollection<ID>, IFieldType
{
//
// ...
//
// hides 'Contains' from ICollection<ID>
new bool Contains(ID item);
}
This worked, but is somewhat of a hack.. and it is specific to just IItemReferenceListField.
- I added another else-if block to the ContentSearchQueryExtensions.cs class which checks the base interfaces of a type as a secondary fallback before resorting to using the 'Contains' method from IEnumerable.
if (typeOfTKey.GetMethods().Any(m => m.Name.Equals(methodName)))
{
var method = typeOfTKey.GenericTypeArguments.Any() ? typeOfTKey.GetMethod(methodName, typeOfTKey.GenericTypeArguments) : typeOfTKey.GetMethod(methodName);
// Useful Large Comment...
expressions = constants.Select(constant => Expression.Call(keySelector.Body, method, constant));
}
else if (typeOfTKey.GetInterfaces().Any(i => i.GetMethods().Any(m => m.Name.Equals(methodName))))
{
// Same as above except, check base interfaces of the typeOfTKey for the "Contains" method
var baseType = typeOfTKey.GetInterfaces().First(i => i.GetMethods().Any(m => m.Name.Equals(methodName)));
var method = baseType.GenericTypeArguments.Any() ? baseType.GetMethod(methodName, baseType.GenericTypeArguments) : baseType.GetMethod(methodName);
expressions = constants.Select(constant => Expression.Call(keySelector.Body, method, constant));
}
else
{
// Useful Large Comment...
var typeArgs = typeOfTKey.IsArray ? new[] { typeOfTKey.GetElementType() } : typeOfTKey.GenericTypeArguments;
expressions = constants.Select(constant => Expression.Call(typeof(Enumerable), methodName, typeArgs, keySelector.Body, constant));
}
If this looks okay, I can send in a PR.
If the current behavior is a bug, please provide the steps to reproduce.
- Setup a synthesis item with a ItemReferenceListField
- Run a content search like the snippet above
What is the expected behavior?
I expected it to work with the ItemReferenceListField field type.
Please mention your Sitecore version and Synthesis version. Sitecore version 9.1 Synthesis version 9.1.0.2-beta1