I have IQueryable type of data collection, I abstracted a method, used as a full-field fuzzy query, at the beginning, I was one by one attribute name to write. Like the following code:
private IQueryable<Tables1> FilterResult(string search, List<Tables1> dtResult)
{
IQueryable<Tables1> results = dtResult.AsQueryable();
results = results.Where(p => (
search == null || (
p.Name != null && p.Name.Contains(search) ||
p.age != null && p.age.ToString().Contains(search) ||
p.sex != null && p.sex.Contains(search) ||
p.content1 != null && p.content1.Contains(search) ||
p.content2 != null && p.content2.Contains(search) ||
p.content3 != null && p.content3.Contains(search)
)
));
return results;
}
But this write if the type of incoming List collection changes, then all the physical attributes have to re-write. So I changed the type of T:
private IQueryable<T> FilterResult(string search, List<T> dtResult,T t)
{
IQueryable<T> results = dtResult.AsQueryable();
//do something
return results;
}
The idea behind this is to get all the attributes of the incoming T type by reflection. Then construct the Lambda expression through Expression Tree.
The question is how do I construct a 'p => p.age.ToString ().Contains(search)'by Expression Tree?
The following is the complete code:
private IQueryable<T> FilterResult(string search, List<T> dtResult, T t)
{
List<Expression> tempExp = new List<Expression>();
var parameter = Expression.Parameter(typeof(T), "p");
foreach (var mi in t.GetType().GetProperties())
{
Expression left = Expression.Property(parameter, t.GetType().GetProperty(mi.Name));
Expression right = Expression.Constant(search, typeof(string));
MethodInfo method;
MethodCallExpression exp;
if (mi.PropertyType == typeof(Int32) || mi.PropertyType == typeof(Int64))
{
//this code is wrong
var exp1 = Expression.Call(Expression.Convert(left, typeof(string)), typeof(object).GetMethod("ToString"));
method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
exp = Expression.Call(exp1, method, right);
}
else
{
method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
exp = Expression.Call(left, method, right);
}
tempExp.Add(exp);
}
Expression all = Expression.Or(Expression.Equal(Expression.Constant(search), null), tempExp[0]);
for (int i = 1; i < tempExp.Count; i++)
{
all = Expression.Or(all, tempExp[i]);
}
var lambda = Expression.Lambda<Func<T, bool>>(all, parameter);
var results = dtResult.Where(lambda.Compile()).AsQueryable(); ;
return results;
}
I'd say it can be easier. I've simplified your Tables
model for presentation.
public class Tables
{
public int Age { get; set; }
public string Name { get; set; }
public string Content { get; set; }
}
public class Matcher
{
private static readonly PropertyInfo[] Properties = typeof(Tables).GetRuntimeProperties().ToArray();
public IQueryable<Tables> FilterResult(string search, List<Tables> dtResult)
{
if(search == null) //Consider using string.IsNullOrWhiteSpace(search) but I wasn't sure if you want to avoid searching for spaces
{
return dtResult.AsQueryable();
}
return dtResult.Where(p => IsMatch(p, search)).AsQueryable();
}
private static bool IsMatch(Tables tables, string search)
{
foreach (var propertyInfo in Properties)
{
var value = propertyInfo.GetValue(tables);
if (value != null && value.ToString().Contains(search))
{
return true;
}
}
return false;
}
}
And here we can put it into work:
class Program
{
public static void Main()
{
const string search = "Bob";
var matcher = new Matcher();
var items = new List<Tables>
{
new Tables {Content = string.Empty, Name = "Bob"}, //This will match
new Tables {Content = "Bob is the best guy.", Name = "Joe"}, //This will also match
new Tables {Content = "Something", Name = null} // This won't null name to verify that nothing unexpected will happen
};
var results = matcher.FilterResult( search, items );
foreach ( var result in results )
{
Console.WriteLine($"Matched the guy named {result.Name}");
}
Console.ReadKey();
}
}
Edit: Here's the generic version
public class Matcher<T>
{
private static readonly PropertyInfo[] Properties = typeof(T).GetRuntimeProperties().ToArray();
public IQueryable<T> FilterResult(string search, List<T> items)
{
if ( search == null) //Consider using string.IsNullOrWhiteSpace(search) but I wasn't sure if you want to avoid searching for spaces
{
return items.AsQueryable();
}
return items.Where(p => IsMatch(p, search)).AsQueryable();
}
private static bool IsMatch(T item, string search)
{
foreach (var propertyInfo in Properties)
{
var value = propertyInfo.GetValue(item);
if (value != null && value.ToString().Contains(search))
{
return true;
}
}
return false;
}
}