子列表上的 NHibernate 通配符可查询扩展
NHibernate wildcard queryable extension on sublists
我正在 IQueryable
上创建一些扩展方法来简化通配符过滤。但是当我尝试过滤子列表时,我遇到了很多异常。我的例子:
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; }
...
}
我的通配符实现不言而喻,该方法需要一个值列表并支持 StartsWith
、EndsWith
和 Contains
。我有一个看起来像这样的 Filter
方法:
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);
}
表达式构建器如下所示:
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;
}
最后我可以像这样使用 Filter
方法:
var query = Session.Query<User>();
query = query.Filter(x => x.FirstName, new [] { "Foo*", "*Bar", "*test*" });
return query;
现在我想对 Address
列表的 Street
属性 进行相同的扩展。在正常的 Linq 中,它将编译为 query.Where(x => x.Addresses.Any(y => y.Street.*wildcardstuff*))
,并且 Filter
方法将像 query.Filter(x => x.Addresses, x => x.Street, values)
一样被调用。但我不断收到 NotSupported 异常。我尝试的最后一件事是 post from EF 但它不是我想要的那种类型。
我最后尝试的实现是这个:
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)));
}
但出现此异常:System.NotSupportedException:无法解析表达式 'x => x.Addresses',因为它具有不受支持的类型。只能解析查询源(即实现 IEnumerable 的表达式)和查询运算符。
我尝试使用通配符逻辑对字符串进行扩展,但这也会引发 NotSupported 异常,有人知道吗?
我会让它更通用。这可能会简化创建此类扩展程序的过程。
query = query.FilterByWildcard(x => x.FirstName, new [] { "Foo*", "*Bar", "*test*" });
并实现:
public static class QueryExtensions
{
public static IQueryable<T> FilterByWildcard<T>(this IQueryable<T> query, Expression<Func<T, string>> prop, IEnumerable<string> items)
{
return query.FilterBy(items, prop, s =>
{
var pattern = s.Trim('*');
if (s.StartsWith("*"))
if (s.EndsWith("*"))
return e => e.Contains(pattern);
else
return e => e.StartsWith(pattern);
else if (s.EndsWith("*"))
return e => e.EndsWith(pattern);
else
return e => e == s;
});
}
public static IQueryable<T> FilterBy<T, TProp, TItem>(this IQueryable<T> query,
IEnumerable<TItem> items,
Expression<Func<T, TProp>> prop,
Func<TItem, Expression<Func<TProp, bool>>> operationSelector, bool isOr = true)
{
var param = prop.Parameters[0];
Expression predicate = null;
foreach (var item in items)
{
var operation = operationSelector(item);
var body = ExpressionReplacer.GetBody(operation, prop.Body);
if (predicate == null)
{
predicate = body;
}
else
{
predicate = Expression.MakeBinary(isOr ? ExpressionType.OrElse : ExpressionType.AndAlso, predicate,
body);
}
}
if (predicate == null)
return query.Where(e => 1 == 2);
var lambda = Expression.Lambda<Func<T, bool>>(predicate, param);
return query.Where(lambda);
}
class ExpressionReplacer : ExpressionVisitor
{
readonly IDictionary<Expression, Expression> _replaceMap;
public ExpressionReplacer(IDictionary<Expression, Expression> replaceMap)
{
_replaceMap = replaceMap ?? throw new ArgumentNullException(nameof(replaceMap));
}
public override Expression Visit(Expression exp)
{
if (exp != null && _replaceMap.TryGetValue(exp, out var replacement))
return replacement;
return base.Visit(exp);
}
public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr)
{
return new ExpressionReplacer(new Dictionary<Expression, Expression> {{toReplace, toExpr}}).Visit(expr);
}
public static Expression Replace(Expression expr, IDictionary<Expression, Expression> replaceMap)
{
return new ExpressionReplacer(replaceMap).Visit(expr);
}
public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace)
{
if (lambda.Parameters.Count != toReplace.Length)
throw new InvalidOperationException();
return new ExpressionReplacer(lambda.Parameters.Zip(toReplace)
.ToDictionary(e => (Expression)e.First, e => e.Second)).Visit(lambda.Body);
}
}
}
这个答案正在修复我自己的方法,但 Svyatoslav Danyliv 的答案更适合未来。
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());
else
condition = GetBooleanExpressionFromStringList(property, values);
Expression<Func<T, bool>> finalCondition = t => innerList.Invoke(t).Any(j => condition.Invoke(j));
return query.Where(finalCondition.Expand());
}
然后像这样使用它。
var query = Session.Query<User>();
query = query.FilterList(x => x.Addresses, y => y.Street, new [] { "Foo*", "*Bar", "*test*" });
return query;
哪个更短更简洁但有限制。
我正在 IQueryable
上创建一些扩展方法来简化通配符过滤。但是当我尝试过滤子列表时,我遇到了很多异常。我的例子:
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; }
...
}
我的通配符实现不言而喻,该方法需要一个值列表并支持 StartsWith
、EndsWith
和 Contains
。我有一个看起来像这样的 Filter
方法:
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);
}
表达式构建器如下所示:
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;
}
最后我可以像这样使用 Filter
方法:
var query = Session.Query<User>();
query = query.Filter(x => x.FirstName, new [] { "Foo*", "*Bar", "*test*" });
return query;
现在我想对 Address
列表的 Street
属性 进行相同的扩展。在正常的 Linq 中,它将编译为 query.Where(x => x.Addresses.Any(y => y.Street.*wildcardstuff*))
,并且 Filter
方法将像 query.Filter(x => x.Addresses, x => x.Street, values)
一样被调用。但我不断收到 NotSupported 异常。我尝试的最后一件事是 post from EF 但它不是我想要的那种类型。
我最后尝试的实现是这个:
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)));
}
但出现此异常:System.NotSupportedException:无法解析表达式 'x => x.Addresses',因为它具有不受支持的类型。只能解析查询源(即实现 IEnumerable 的表达式)和查询运算符。 我尝试使用通配符逻辑对字符串进行扩展,但这也会引发 NotSupported 异常,有人知道吗?
我会让它更通用。这可能会简化创建此类扩展程序的过程。
query = query.FilterByWildcard(x => x.FirstName, new [] { "Foo*", "*Bar", "*test*" });
并实现:
public static class QueryExtensions
{
public static IQueryable<T> FilterByWildcard<T>(this IQueryable<T> query, Expression<Func<T, string>> prop, IEnumerable<string> items)
{
return query.FilterBy(items, prop, s =>
{
var pattern = s.Trim('*');
if (s.StartsWith("*"))
if (s.EndsWith("*"))
return e => e.Contains(pattern);
else
return e => e.StartsWith(pattern);
else if (s.EndsWith("*"))
return e => e.EndsWith(pattern);
else
return e => e == s;
});
}
public static IQueryable<T> FilterBy<T, TProp, TItem>(this IQueryable<T> query,
IEnumerable<TItem> items,
Expression<Func<T, TProp>> prop,
Func<TItem, Expression<Func<TProp, bool>>> operationSelector, bool isOr = true)
{
var param = prop.Parameters[0];
Expression predicate = null;
foreach (var item in items)
{
var operation = operationSelector(item);
var body = ExpressionReplacer.GetBody(operation, prop.Body);
if (predicate == null)
{
predicate = body;
}
else
{
predicate = Expression.MakeBinary(isOr ? ExpressionType.OrElse : ExpressionType.AndAlso, predicate,
body);
}
}
if (predicate == null)
return query.Where(e => 1 == 2);
var lambda = Expression.Lambda<Func<T, bool>>(predicate, param);
return query.Where(lambda);
}
class ExpressionReplacer : ExpressionVisitor
{
readonly IDictionary<Expression, Expression> _replaceMap;
public ExpressionReplacer(IDictionary<Expression, Expression> replaceMap)
{
_replaceMap = replaceMap ?? throw new ArgumentNullException(nameof(replaceMap));
}
public override Expression Visit(Expression exp)
{
if (exp != null && _replaceMap.TryGetValue(exp, out var replacement))
return replacement;
return base.Visit(exp);
}
public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr)
{
return new ExpressionReplacer(new Dictionary<Expression, Expression> {{toReplace, toExpr}}).Visit(expr);
}
public static Expression Replace(Expression expr, IDictionary<Expression, Expression> replaceMap)
{
return new ExpressionReplacer(replaceMap).Visit(expr);
}
public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace)
{
if (lambda.Parameters.Count != toReplace.Length)
throw new InvalidOperationException();
return new ExpressionReplacer(lambda.Parameters.Zip(toReplace)
.ToDictionary(e => (Expression)e.First, e => e.Second)).Visit(lambda.Body);
}
}
}
这个答案正在修复我自己的方法,但 Svyatoslav Danyliv 的答案更适合未来。
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());
else
condition = GetBooleanExpressionFromStringList(property, values);
Expression<Func<T, bool>> finalCondition = t => innerList.Invoke(t).Any(j => condition.Invoke(j));
return query.Where(finalCondition.Expand());
}
然后像这样使用它。
var query = Session.Query<User>();
query = query.FilterList(x => x.Addresses, y => y.Street, new [] { "Foo*", "*Bar", "*test*" });
return query;
哪个更短更简洁但有限制。