我創建2種擴展方法WhereFilter()
爲IEnumerable
和IQueryable
。 通過這種方式,您也可以將此過濾器用於例如實體框架並且是在服務器上執行的過濾。
我使用了基於過濾*(不是?),所以我可以使用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 used{0}. '*' can maximum occur 2 times.", fieldName == null ? "" : " for '" + fieldName + "'"));
if (filter.Contains("?"))
throw new ApplicationException(string.Format("Invalid filter used{0}. '?' 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 used{0}.", 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 used{0}. '*' can maximum occur 2 times.", fieldName==null?"":" for '" + fieldName + "'"));
if (filter.Contains("?"))
throw new ApplicationException(string.Format("Invalid filter used{0}. '?' 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 used{0}.", fieldName == null ? "" : " for '" + fieldName + "'"));
// Empty string: all results
return source;
}
}
以及如何做你的工作LIKE?我可以幫助你構建一個表達式,但是我需要了解你想如何首先工作...正則表達式?包含?等等? – 2009-06-05 19:49:30
這沒關係。最終的實現將可能與正則表達式。基本上我有一個Func來傳遞2個字符串,並得到true或false作爲returnvalue。 我的問題是,我不明白System.Linq.Expressions.Expression命名空間中的對象的實現,它似乎是Func (查看binaryOpFactory的泛型類型),所以我無法創建我自己的比較。 –
2009-06-05 20:07:43
重新評論:理解Expression API可能需要做一些工作......我嘗試在我的博客上介紹一些基礎知識; Jon的書(C#深入)也給出了高層次的概述。 – 2009-06-08 12:23:27