如何为 Like 创建 System.Linq.Expressions.Expression?
Posted
技术标签:
【中文标题】如何为 Like 创建 System.Linq.Expressions.Expression?【英文标题】:How to create a System.Linq.Expressions.Expression for Like? 【发布时间】:2010-10-31 16:49:57 【问题描述】:我创建了一个可过滤的 BindingList from this source。效果很好:
list.Filter("Customer == 'Name'");
做它应该做的。内部工作就像一个解析器,它将表达式==
或!=
转换为System.Linq.Expressions.Expression
。在这种情况下,==
变为 System.Linq.Expressions.Expression.Equal
。
很遗憾System.Linq.Expressions.Expression
不包含like 运算符,我不知道如何解决。
初始代码如下所示:
private static Dictionary<String, Func<Expression, Expression, Expression>>
binaryOpFactory = new Dictionary<String, Func<Expression, Expression, Expression>>();
static Init()
binaryOpFactory.Add("==", Expression.Equal);
binaryOpFactory.Add(">", Expression.GreaterThan);
binaryOpFactory.Add("<", Expression.LessThan);
binaryOpFactory.Add(">=", Expression.GreaterThanOrEqual);
binaryOpFactory.Add("<=", Expression.LessThanOrEqual);
binaryOpFactory.Add("!=", Expression.NotEqual);
binaryOpFactory.Add("&&", Expression.And);
binaryOpFactory.Add("||", Expression.Or);
然后我创建了一个表达式来做我想做的事:
private static System.Linq.Expressions.Expression<Func<String, String, bool>>
Like_Lambda = (item, search) => item.ToLower().Contains(search.ToLower());
private static Func<String, String, bool> Like = Like_Lambda.Compile();
例如
Console.WriteLine(like("McDonalds", "donAld")); // true
Console.WriteLine(like("McDonalds", "King")); // false
但是binaryOpFactory
需要这个:
Func<Expression, Expression, Expression>
预定义的表达式似乎正是这样:
System.Linq.Expressions.Expression.Or;
谁能告诉我如何转换我的表达方式?
【问题讨论】:
你的 LIKE 是如何运作的?我可以帮助您构建一个表达式,但我需要先了解您希望它如何工作......正则表达式?包含?等等? 没关系。最终的实现可能会使用正则表达式。基本上我有一个 Func类似:
static IEnumerable<T> WhereLike<T>(
this IEnumerable<T> data,
string propertyOrFieldName,
string value)
var param = Expression.Parameter(typeof(T), "x");
var body = Expression.Call(
typeof(Program).GetMethod("Like",
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public),
Expression.PropertyOrField(param, propertyOrFieldName),
Expression.Constant(value, typeof(string)));
var lambda = Expression.Lambda<Func<T, bool>>(body, param);
return data.Where(lambda.Compile());
static bool Like(string a, string b)
return a.Contains(b); // just for illustration
就Func<Expression,Expression,Expression>
而言:
static Expression Like(Expression lhs, Expression rhs)
return Expression.Call(
typeof(Program).GetMethod("Like",
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)
,lhs,rhs);
【讨论】:
看起来不错,但我需要……返回一个 Funcp => p.Prop.Contains(val)
,如下所示:Expression op = Expression.Call(lhs, "Contains", Type.EmptyTypes, rhs);
。【参考方案2】:
我为IEnumerable
和IQueryable
创建了两个扩展方法WhereFilter()
。
这样,您也可以将此过滤器与例如Entity Framework 和是在服务器上执行的过滤。
我使用了一个基于 * 的过滤器(不是?),所以我可以使用底层 Linq 方法 StartsWith()
、EndsWith()
和 Contains()
。支持的格式:A*、*A、*A*、A*B
用法:
var filtered = list.WhereFilter(i => i.Name, "a*", "First Name");
这里是类的基础知识:
/// <summary>
/// Extension Methods for Filtering on IQueryable and IEnumerable
/// </summary>
internal static class WhereFilterExtensions
/// <summary>
/// Filters a sequence of values based on a filter with asterix characters: A*, *A, *A*, A*B
/// </summary>
/// <param name="source"></param>
/// <param name="selector">Field to use for filtering. (E.g: item => item.Name)</param>
/// <param name="filter">Filter: A*, *A, *A*, A*B</param>
/// <param name="fieldName">Optional description of filter field used in error messages</param>
/// <returns>Filtered source</returns>
public static IEnumerable<T> WhereFilter<T>(this IEnumerable<T> source, Func<T, string> selector, string filter, string fieldName)
if (filter == null)
return source;
if (selector == null)
return source;
int astrixCount = filter.Count(c => c.Equals('*'));
if (astrixCount > 2)
throw new ApplicationException(string.Format("Invalid filter used0. '*' can maximum occur 2 times.", fieldName == null ? "" : " for '" + fieldName + "'"));
if (filter.Contains("?"))
throw new ApplicationException(string.Format("Invalid filter used0. '?' is not supported, only '*' is supported.", fieldName == null ? "" : " for '" + fieldName + "'"));
// *XX*
if (astrixCount == 2 && filter.Length > 2 && filter.StartsWith("*") && filter.EndsWith("*"))
filter = filter.Replace("*", "");
return source.Where(item => selector.Invoke(item).Contains(filter));
// *XX
if (astrixCount == 1 && filter.Length > 1 && filter.StartsWith("*"))
filter = filter.Replace("*", "");
return source.Where(item => selector.Invoke(item).EndsWith(filter));
// XX*
if (astrixCount == 1 && filter.Length > 1 && filter.EndsWith("*"))
filter = filter.Replace("*", "");
return source.Where(item => selector.Invoke(item).StartsWith(filter));
// X*X
if (astrixCount == 1 && filter.Length > 2 && !filter.StartsWith("*") && !filter.EndsWith("*"))
string startsWith = filter.Substring(0, filter.IndexOf('*'));
string endsWith = filter.Substring(filter.IndexOf('*') + 1);
return source.Where(item => selector.Invoke(item).StartsWith(startsWith) && selector.Invoke(item).EndsWith(endsWith));
// XX
if (astrixCount == 0 && filter.Length > 0)
return source.Where(item => selector.Invoke(item).Equals(filter));
// *
if (astrixCount == 1 && filter.Length == 1)
return source;
// Invalid Filter
if (astrixCount > 0)
throw new ApplicationException(string.Format("Invalid filter used0.", fieldName == null ? "" : " for '" + fieldName + "'"));
// Empty string: all results
return source;
/// <summary>
/// Filters a sequence of values based on a filter with asterix characters: A*, *A, *A*, A*B
/// </summary>
/// <param name="source"></param>
/// <param name="selector">Field to use for filtering. (E.g: item => item.Name) </param>
/// <param name="filter">Filter: A*, *A, *A*, A*B</param>
/// <param name="fieldName">Optional description of filter field used in error messages</param>
/// <returns>Filtered source</returns>
public static IQueryable<T> WhereFilter<T>(this IQueryable<T> source, Expression<Func<T, string>> selector, string filter, string fieldName)
if (filter == null)
return source;
if (selector == null)
return source;
int astrixCount = filter.Count(c => c.Equals('*'));
if (astrixCount > 2)
throw new ApplicationException(string.Format("Invalid filter used0. '*' can maximum occur 2 times.", fieldName==null?"":" for '" + fieldName + "'"));
if (filter.Contains("?"))
throw new ApplicationException(string.Format("Invalid filter used0. '?' is not supported, only '*' is supported.", fieldName == null ? "" : " for '" + fieldName + "'"));
// *XX*
if (astrixCount == 2 && filter.Length > 2 && filter.StartsWith("*") && filter.EndsWith("*"))
filter = filter.Replace("*", "");
return source.Where(
Expression.Lambda<Func<T, bool>>(
Expression.Call(selector.Body, "Contains", null, Expression.Constant(filter)),
selector.Parameters[0]
)
);
// *XX
if (astrixCount == 1 && filter.Length > 1 && filter.StartsWith("*"))
filter = filter.Replace("*", "");
return source.Where(
Expression.Lambda<Func<T, bool>>(
Expression.Call(selector.Body, "EndsWith", null, Expression.Constant(filter)),
selector.Parameters[0]
)
);
// XX*
if (astrixCount == 1 && filter.Length > 1 && filter.EndsWith("*"))
filter = filter.Replace("*", "");
return source.Where(
Expression.Lambda<Func<T, bool>>(
Expression.Call(selector.Body, "StartsWith", null, Expression.Constant(filter)),
selector.Parameters[0]
)
);
// X*X
if (astrixCount == 1 && filter.Length > 2 && !filter.StartsWith("*") && !filter.EndsWith("*"))
string startsWith = filter.Substring(0, filter.IndexOf('*'));
string endsWith = filter.Substring(filter.IndexOf('*') + 1);
return source.Where(
Expression.Lambda<Func<T, bool>>(
Expression.Call(selector.Body, "StartsWith", null, Expression.Constant(startsWith)),
selector.Parameters[0]
)
).Where(
Expression.Lambda<Func<T, bool>>(
Expression.Call(selector.Body, "EndsWith", null, Expression.Constant(endsWith)),
selector.Parameters[0]
)
);
// XX
if (astrixCount == 0 && filter.Length > 0)
return source.Where(
Expression.Lambda<Func<T, bool>>(
Expression.Equal(selector.Body, Expression.Constant(filter)),
selector.Parameters[0]
)
);
// *
if (astrixCount == 1 && filter.Length == 1)
return source;
// Invalid Filter
if (astrixCount > 0)
throw new ApplicationException(string.Format("Invalid filter used0.", fieldName == null ? "" : " for '" + fieldName + "'"));
// Empty string: all results
return source;
【讨论】:
by '与例如实体框架和在服务器上执行的过滤'你的意思是在数据库 - 即:翻译成 SQL 吗?我的印象是selector.Invoke
阻止了 EF 转换为 SQL?
是的,ExpressionTree 由“LINQ Provider”转换。使用 Enity Framework 时,LINQ to Entities 提供程序会将其转换为将在数据库上执行的 SQL 字符串。结果将不是数组,而是一个遍历 DataReader 的 IEnumerable。以上是关于如何为 Like 创建 System.Linq.Expressions.Expression?的主要内容,如果未能解决你的问题,请参考以下文章