Skip to content

ContainsOr with an ItemReferenceListField doesn't work #89

@sitecorepm

Description

@sitecorepm

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, 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.

  1. 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
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.

  1. 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.

  1. Setup a synthesis item with a ItemReferenceListField
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions