private static IEnumerable <MemberInfo> GetAvailableMembers(Type type, CompatiblityMode mode) { var flags = BindingFlags.Public | BindingFlags.Instance; if (mode == CompatiblityMode.Strict) { return(type .GetProperties(flags) .Where(m => m.CanWrite && m.MemberType == MemberTypes.Property)); } else { return(type .GetFields(flags).Cast <MemberInfo>() .Concat(type.GetProperties(flags)).ToArray() .Where(m => m.MemberType == MemberTypes.Property || m.MemberType == MemberTypes.Field)); } }
/// <summary> /// Filters entries by all their string fields /// </summary> /// <param name="searchQuery">The search query by which the entries should be filtered</param> /// <returns>A filtered collection of entries</returns> public static IQueryable <T> Search <T>(this IQueryable <T> data, string searchQuery, CompatiblityMode mode = CompatiblityMode.Strict) { // Simply return the entire collection, if no search query is specified if (string.IsNullOrWhiteSpace(searchQuery)) { return(data); } var matches = data; // Split the search query and construct predicates for each foreach (var part in searchQuery.ToUpperInvariant().Split()) { if (!string.IsNullOrWhiteSpace(part)) { matches = matches.Where(SearchHelper.ConstructSearchPredicate <T>(part.Trim(), mode)); } } return(matches); }
/// <summary> /// Constructs an expression that is used to filter entries and execute a search for the specified query /// on all string type fields of an entity /// </summary> /// <param name="query">The query to be searched for in all string fields</param> /// <returns>An expression that can be used in queries to the DB context</returns> /// /// Example: /// For an entity with string properties `Name` and `Address`, the resulting expression /// is something like this: /// /// `x => x.Name.ToLower().Contains(query) || x.Address.ToLower().Contains(query)` /// internal static Expression <Func <T, bool> > ConstructSearchPredicate <T>(string query, CompatiblityMode mode) { // Create constant with query var constant = Expression.Constant(query); // Get all object properties var type = typeof(T); // Input parameter (e.g. "c => ") var parameter = Expression.Parameter(type, "c"); // Get appropriate members to test var members = GetAvailableMembers(type, mode); // Construct expression Expression finalExpression = null; foreach (var m in members) { // Get type of p var underlyingType = m.GetUnderlyingType(); // In Strict mode, only string properties are taken into account, otherwise all props and fields are (except collections) if ((mode == CompatiblityMode.Strict && underlyingType == typeof(string)) || (mode == CompatiblityMode.All && !underlyingType.IsCollection())) { // Express a member (e.g. "c.<member>" ) Expression memberExpression; if (m.MemberType == MemberTypes.Field) { memberExpression = Expression.Field(parameter, m.Name); } else { memberExpression = Expression.Property(parameter, m.Name); } // Get query expression (e.g. "c.<member>.ToString().ToUpperInvariant().Contains(<constant>)") var partialExpression = GetQueryExpression(memberExpression, constant, underlyingType, mode); // Handle case when no OR operation can be constructed (it's the first condition) if (finalExpression == null) { finalExpression = partialExpression; } else { finalExpression = Expression.OrElse(finalExpression, partialExpression); } } } // Return the constructed expression return(Expression.Lambda <Func <T, bool> >(finalExpression, parameter)); }
/// <summary> /// Constructs an expression to query the property for the specified search term /// </summary> /// <param name="propertyExpression">The expression of the property being tested</param> /// <param name="queryConstant">The expression representing the search term</param> /// <param name="propertyType">The type of the property to be tested</param> /// The resulting expression will test the property for inclusion of the query /// (e.g. "c.<property>.ToString().ToLowerInvariant().Contains(<queryConstant>)") private static Expression GetQueryExpression(Expression propertyExpression, Expression queryConstant, Type propertyType, CompatiblityMode mode) { // Check that property value is not null (or default) (e.g. "c.<property> != null") Expression nullCheckExpression = null; // Value types can safely be operated on, since they have non-null default values if (!propertyType.IsValueType) { nullCheckExpression = Expression.NotEqual(propertyExpression, Expression.Constant(null, propertyType)); } var transformedProperty = propertyExpression; // In strict mode, ToString and ToUpperInvariant cannot be used (provider might not know how to translate them) if (mode == CompatiblityMode.Strict) { // Run uppercase method on property (e.g. "c.<property>.ToUpper()") transformedProperty = Expression.Call(transformedProperty, UpperMethod); } else { // Find the ToString method that should be executed for the specific type var toStringMethod = propertyType.GetMethod("ToString", new Type[0]); // Run ToString method on property (e.g. "c.<property>.ToString()") transformedProperty = Expression.Call(propertyExpression, toStringMethod); // Run uppercase method on property (e.g. "c.<property>.ToString().ToUpperInvariant()") transformedProperty = Expression.Call(transformedProperty, UpperInvariantMethod); } // Run contains on property with provided query (e.g. "c.<property>.ToString().ToUpperInvariant().Contains(<query>)") transformedProperty = Expression.Call(transformedProperty, ContainsMethod, queryConstant); if (nullCheckExpression == null) { return(transformedProperty); } else { return(Expression.AndAlso(nullCheckExpression, transformedProperty)); } }