I am trying to extend GraphQL Filtering to query multiple words. E.g.: if parsedValue = "Tom and Jerry", then the contains should get me items that contain either 'Tom' or 'and' or 'jerry'.
I am using ChilliCream HotChocolate .NET code for the backend. I have extended the filter as:
public class MyGqlFilterConvention : FilterConvention
{
protected override void Configure(IFilterConventionDescriptor descriptor)
{
descriptor.AddDefaultOperations();
descriptor.AddDefaults();
descriptor.
AddProviderExtension(new QueryableFilterProviderExtension(y =>
y.AddFieldHandler<MyStringContainsHandler>()));
}
}
public class MyStringContainsHandler : QueryableStringOperationHandler
{
protected override int Operation => DefaultFilterOperations.Contains;
public override Expression HandleOperation(QueryableFilterContext context,
IFilterOperationField field, IValueNode value, object parsedValue)
{
var property = context.GetInstance();
// E.g.: parsedValue = "Tom and Jerry"
return FilterExpressionBuilder.Contains(Expression.Call(property), parsedValue);
}
}
At the moment, if the parsedValue is "Tom and Jerry", then the query returns results containing exactly "Tom and Jerry". But my requirement is to get results that contain either 'Tom' or 'and' or 'jerry'. How can I achieve that? Thanks.
I was working on a graphql project this weekend. What about splitting the 'parsedValue' into multiple words and construct an Or expression for each word?
public class MyStringContainsHandler : QueryableStringOperationHandler
{
protected override int Operation => DefaultFilterOperations.Contains;
public override Expression HandleOperation(QueryableFilterContext context,
IFilterOperationField field, IValueNode value, object parsedValue)
{
var property = context.GetInstance();
if (parsedValue is string stringValue)
{
var words = stringValue.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (words.Length == 1)
{
return FilterExpressionBuilder.Contains(Expression.Call(property), parsedValue);
}
else
{
Expression filterExpression = null;
foreach (var word in words)
{
var wordExpression = FilterExpressionBuilder.Contains(Expression.Call(property), word);
if (filterExpression == null)
{
filterExpression = wordExpression;
}
else
{
filterExpression = Expression.OrElse(filterExpression, wordExpression);
}
}
return filterExpression;
}
}
return null;
}
}
Thinking, check if parsedValue is a string, then split it into words using 'StringSplitOptions.RemoveEmptyEntries' to remove empty entries. Then if it's one word, calls the base implementation. If not, it'll create an Or expression for each word and combine them using Expression.OrElse. This way the filtering expression will match items that contain any of the words in the parsedValue string