I'm creating some extension methods on IQueryable
to make wildcard filtering easier. But I'm stumbling into a lot of exceptions when I try to filter a sub list. My example:
public class User {
public string FirstName { get; set; }
public string LastName { get; set; }
public IReadOnlyList<Address> Addresses { get; set; }
...
}
public class Address {
public string Street { get; set; }
...
}
My wildcard implementation speaks for itself, the method expects a list of values and supports StartsWith
, EndsWith
and Contains
. I have a Filter
method that looks like this:
public static IQueryable<T> Filter<T>(this IQueryable<T> query, Expression<Func<T, string>> property,
IList<string> values)
{
if (values == null || values.Count == 0)
return query;
Expression<Func<T, bool>> condition;
if (values.Count == 1)
condition = GetBooleanExpressionFromString(property, values.First()).Expand();
else
condition = GetBooleanExpressionFromStringList(property, values).Expand();
return query.Where(condition);
}
And the expressions builders look like:
private static Expression<Func<T, bool>> GetBooleanExpressionFromStringList<T>(
Expression<Func<T, string>> expression,
IList<string> values)
{
var predicate = PredicateBuilder.New<T>();
foreach (var value in values)
{
var parsedValue = value.Replace("*", string.Empty);
if (value.StartsWith("*") && value.EndsWith("*"))
predicate.Or(x => expression.Invoke(x).Contains(parsedValue));
else if (value.StartsWith("*"))
predicate.Or(x => expression.Invoke(x).EndsWith(parsedValue));
else if (value.EndsWith("*"))
predicate.Or(x => expression.Invoke(x).StartsWith(parsedValue));
else predicate.Or(x => expression.Invoke(x) == parsedValue);
}
return predicate;
}
public static Expression<Func<T, bool>> GetBooleanExpressionFromString<T>(
Expression<Func<T, string>> expression,
string value)
{
var parsedValue = value.Replace("*", string.Empty);
if (value.StartsWith("*") && value.EndsWith("*"))
return x => expression.Invoke(x).Contains(parsedValue);
if (value.StartsWith("*"))
return x => expression.Invoke(x).EndsWith(parsedValue);
if (value.EndsWith("*"))
return x => expression.Invoke(x).StartsWith(parsedValue);
return x => expression.Invoke(x) == parsedValue;
}
At the end I can use the Filter
method like this:
var query = Session.Query<User>();
query = query.Filter(x => x.FirstName, new [] { "Foo*", "*Bar", "*test*" });
return query;
Now I want to make the same extension for the Street
property of the Address
list. In normal Linq it would compile to query.Where(x => x.Addresses.Any(y => y.Street.*wildcardstuff*))
and the Filter
method would be called like query.Filter(x => x.Addresses, x => x.Street, values)
. But I'm keep getting NotSupported exceptions. The last thing I tried is this post from EF but its not that typed like i want it to be.
The last implementation I tried is this one:
public static IQueryable<T> FilterList<T, U>(this IQueryable<T> query, Expression<Func<T, IEnumerable<U>>> innerList, Expression<Func<U, string>> property,
IList<string> values)
{
if (values == null || values.Count == 0)
return query;
Expression<Func<U, bool>> condition;
if (values.Count == 1)
condition = GetBooleanExpressionFromString(property, values.First()).Expand();
else
condition = GetBooleanExpressionFromStringList(property, values).Expand();
return query.Where(i => innerList.Invoke(i).Any(j => condition.Invoke(j)));
}
but got this exception: System.NotSupportedException: Cannot parse expression 'x => x.Addresses' as it has an unsupported type. Only query sources (that is, expressions that implement IEnumerable) and query operators can be parsed. I tried to make an extension on string with the wildcard logic but this throws also a NotSupported exception, anyone has an idea?