I am looking for a generic Filter for the searchText in the query of any Column/Field mapping
public static IQueryable<T> Filter<T>(this IQueryable<T> source, string searchTerm)
{
var propNames = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(e=>e.PropertyType == typeof(String)).Select(x => x.Name).ToArray();
//I am getting the property names but How can I create Expression for
source.Where(Expression)
}
Here I am giving you an example scenario
Now From my HTML5 Table in Asp.net MVC4 , I have provided a Search box to filter results of entered text , which can match any of the below columns/ Menu class Property values , and I want to do this search in Server side , how can I implement it.
EF Model Class
public partial class Menu
{
public int Id { get; set; }
public string MenuText { get; set; }
public string ActionName { get; set; }
public string ControllerName { get; set; }
public string Icon { get; set; }
public string ToolTip { get; set; }
public int RoleId { get; set; }
public virtual Role Role { get; set; }
}
You can use Expressions:
private static Expression<Func<T, bool>> GetColumnEquality<T>(string property, string term)
{
var obj = Expression.Parameter(typeof(T), "obj");
var objProperty = Expression.PropertyOrField(obj, property);
var objEquality = Expression.Equal(objProperty, Expression.Constant(term));
var lambda = Expression.Lambda<Func<T, bool>>(objEquality, obj);
return lambda;
}
public static IQueryable<T> Filter<T>(IQueryable<T> source, string searchTerm)
{
var propNames = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(e => e.PropertyType == typeof(string))
.Select(x => x.Name).ToList();
var predicate = PredicateBuilder.False<T>();
foreach(var name in propNames)
{
predicate = predicate.Or(GetColumnEquality<T>(name, searchTerm));
}
return source.Where(predicate);
}
Combined with the name PredicateBuilder from C# in a NutShell. Which is also a part of LinqKit.
Example:
public class Foo
{
public string Bar { get; set; }
public string Qux { get; set; }
}
Filter<Foo>(Enumerable.Empty<Foo>().AsQueryable(), "Hello");
// Expression Generated by Predicate Builder
// f => ((False OrElse Invoke(obj => (obj.Bar == "Hello"), f)) OrElse Invoke(obj => (obj.Qux == "Hello"), f))
void Main()
{
// creates a clause like
// select * from Menu where MenuText like '%ASD%' or ActionName like '%ASD%' or....
var items = Menu.Filter("ASD").ToList();
}
// Define other methods and classes here
public static class QueryExtensions
{
public static IQueryable<T> Filter<T>(this IQueryable<T> query, string search)
{
var properties = typeof(T).GetProperties().Where(p =>
/*p.GetCustomAttributes(typeof(System.Data.Objects.DataClasses.EdmScalarPropertyAttribute),true).Any() && */
p.PropertyType == typeof(String));
var predicate = PredicateBuilder.False<T>();
foreach (var property in properties )
{
predicate = predicate.Or(CreateLike<T>(property,search));
}
return query.AsExpandable().Where(predicate);
}
private static Expression<Func<T,bool>> CreateLike<T>( PropertyInfo prop, string value)
{
var parameter = Expression.Parameter(typeof(T), "f");
var propertyAccess = Expression.MakeMemberAccess(parameter, prop);
var like = Expression.Call(propertyAccess, "Contains", null, Expression.Constant(value,typeof(string)));
return Expression.Lambda<Func<T, bool>>(like, parameter);
}
}
You need to add reference to LinqKit to use PredicateBuilder and AsExpandable method
otherwise won't work with EF, only with Linq to SQL
If you want Col1 like '%ASD%' AND Col2 like '%ASD%' et, change PredicateBuilder.False to PredicateBuilder.True and predicate.Or to predicate.And
Also you need to find a way to distinguish mapped properties by your own custom properties (defined in partial classes for example)
Related
I'm trying to convert a Lambda expression from a domain to another.
This is what I receive as parameter:
Expression<Func<Entities.UserRight, bool>> expression
And I should return ane expression of type
Expression<Func<UserRight,bool>>
Both UserRight and Entities.UserRight are same models, but belongs to diferent nugget packages.
I'm trying by:
public Expression<Func<UserRight,bool>> ConvertExpression(Expression<Func<Entities.UserRight, bool>> expression)
{
var resultBody = Expression.Convert(expression.Body, typeof(UserRight));
var result = Expression.Lambda<Func<UserRight, bool>>(resultBody, expression.Parameters);
return result;
}
But I receive an error of InvalidOperationException
Suppose we have two class Address1 and Address2 like bellow:
class Address1
{
public string City { get; set; }
public string Detail { get; set; }
}
class Address2
{
public string City { get; set; }
public string Detail { get; set; }
}
and whe have an expression for Address1 like this:
Expression<Func<Address1, string>> getCityName = ad1 => ad1.City;
We can change the expression to be usable for Address2 by an ExpressionVisitor:
public class ExpressionConvertor: ExpressionVisitor
{
private ParameterExpression ad2Parameter;
public ExpressionConvertor(ParameterExpression ad2Parameter)
{
this.ad2Parameter = ad2Parameter;
}
public override Expression Visit(Expression node)
{
if(node is LambdaExpression node2)
{
var exp = base.Visit(node2.Body);
return Expression.Lambda(exp, ad2Parameter);
}
return base.Visit(node);
}
protected override Expression VisitMember(MemberExpression node)
{
if(node.Expression is ParameterExpression)
return Expression.PropertyOrField(ad2Parameter, node.Member.Name);
return base.VisitMember(node);
}
}
and finally we use this class like this:
Expression<Func<Address1, string>> getCityName = ad1 => ad1.City;
var ad2Parameter = Expression.Parameter(typeof(Address2), "ad2");
var convertor = new ExpressionConvertor(ad2Parameter);
var getCityName2 = (Expression<Func<Address2, string>>)convertor.Visit(getCityName);
var getCityName2Method = getCityName2.Compile();
var addr2 = new Address2()
{
City = "MyCity",
Detail = "My Address Detail"
};
var cityName = getCityName2Method(addr2);
Console.WriteLine(cityName); //MyCity
*****Scroll down for final working solution*****
All of my Entity Framework models use partials, which implement my own IEntity interface:
public interface IEntity
{
int Status { get; set; }
int ID { get; set; }
}
This allows me to filter any Entity which implements this interface, based on the following function (simplified version):
public static IQueryable<T> FilterByStatus<T>(this IQueryable<T> query, int status) where T : class, IEntity
{
return query.Where(m => m.Status == status);
}
Now I want a function which names all of the properties, which I might want to perform a text query on. Let's say that implementation Foo of IEntity has 2 values (Bar and Baz) that I want to perform queries on.
I currently have:
public static IQueryable<Foo> FooSearch(this Entities context, string query)
{
IQueryable<Foo> result = context.Foo;
if (!String.IsNullOrEmpty(query))
{
result = result.Where(m =>
m.Bar.ToLower().IndexOf(query.ToLower()) >= 0 ||
m.Baz.ToLower().IndexOf(query.ToLower()) >= 0);
}
return result;
}
But I want to set it up in a more generic way. Something like:
public interface IEntity
{
int Status { get; set; }
int ID { get; set; }
string[] QueryableProperties { get; set; }
}
And some kind of implementation like (pseudocode):
public static IQueryable<T> GenericSearch(this IQueryable<T> query, string query) where T : class, IEntity
{
if (!String.IsNullOrEmpty(query))
{
query = query.Where(m =>
m[QueryableProperties[0]].ToLower().IndexOf(query.ToLower()) >= 0 ||
m[QueryableProperties[1]].ToLower().IndexOf(query.ToLower()) >= 0 ||
// .... //
m[QueryableProperties[QueryableProperties.Count - 1]].ToLower().IndexOf(query.ToLower()) >= 0)
}
return query;
}
How can I achieve this?
******Working Solution******
Search function:
public static class SearchFilter
{
private static Expression GetNestedPropertyExpression(Expression expression, string propertyName)
{
Expression body = expression;
foreach (var member in propertyName.Split('.'))
{
body = Expression.PropertyOrField(body, member);
}
return body;
}
private static Expression<Func<T, bool>> GetSearchExpression<T>(string[] propertyNames, string query)
{
var parameterExp = Expression.Parameter(typeof(T), "category");
MethodInfo containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
MethodInfo toLowerMethod = typeof(string).GetMethod("ToLower", Type.EmptyTypes);
List<Expression> methodCalls = new List<Expression>();
foreach (string propertyName in propertyNames)
{
var propertyExp = GetNestedPropertyExpression(parameterExp, propertyName);
var queryValue = Expression.Constant(query.ToLower(), typeof(string));
var toLowerMethodExp = Expression.Call(propertyExp, toLowerMethod);
var containsMethodExp = Expression.Call(toLowerMethodExp, containsMethod, queryValue);
methodCalls.Add(containsMethodExp);
}
var orExp = methodCalls.Aggregate((left, right) => Expression.Or(left, right));
return Expression.Lambda<Func<T, bool>>(orExp, parameterExp);
}
public static IQueryable<T> Search<T>(this IQueryable<T> query, string property) where T : class, IEntity
{
var filterAttributes = typeof(T).GetCustomAttributes(
typeof(FilterableAttribute), true
).FirstOrDefault() as FilterableAttribute;
if (filterAttributes == null) {
return query;
}
var filterableColumns = filterAttributes.FilterableAttributes;
if (filterableColumns == null || filterableColumns.Count() == 0)
{
return query;
}
if (property == null)
{
return query;
}
return query.Where(GetSearchExpression<T>(filterableColumns, property));
}
}
Decorator (example: both a property of my model, and a nested property):
[Filterable(FilterableAttributes = new string[] {
nameof(Foo),
nameof(Bar) + "." + nameof(Models.MyConnectedModel.Baz)
})]
public partial class MyConnectedModel: IEntity
{
}
Nice question :)
Here's how you can do this:
static Expression<Func<T, bool>> GetExpression<T>(string[] propertyNames, string query)
{
var parameterExp = Expression.Parameter(typeof(T), "category");
MethodInfo containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
MethodInfo toLowerMethod = typeof(string).GetMethod("ToLower",Type.EmptyTypes);
List<Expression> methodCalls = new List<Expression>();
foreach (string propertyName in propertyNames)
{
var propertyExp = Expression.Property(parameterExp, propertyName);
var queryValue = Expression.Constant(query.ToLower(), typeof(string));
var toLowerMethodExp = Expression.Call(propertyExp, toLowerMethod);
var containsMethodExp = Expression.Call(toLowerMethodExp, containsMethod, queryValue);
methodCalls.Add(containsMethodExp);
}
var orExp = methodCalls.Aggregate((left, right) => Expression.Or(left, right));
return Expression.Lambda<Func<T, bool>>(orExp, parameterExp);
}
And then you can use it like this (query is an IQueryable<MyEntity>)
query=query.Where(GetExpression<MyEntity>(queryableProperties,"SomeValue"));
IMO, it can be surprisingly easy:
IQueryable<T> GenericSearch<T>(this IQueryable<T> items, string query)
{
var queryableProperties = items
.First()
.GetType()
.GetProperties()
.Where(p => p.PropertyType == typeof(string))
.ToList();
return items.Where(i => queryableProperties.Any(p => ((string)p.GetValue(i)).Contains(query)));
}
This searches all properties of type string in a list of items.
It can be a class method, an extension method, a static function, it's easy to understand and works well. It can be extended to Fields or restricted to some interfaces easily.
Adjust to your liking.
class someClass
{
public int Id { get; set; }
public string Name{ get; set; }
...
public string someProperty { get; set; }
}
Expression<Func<someClass, object>> selector = null;
selector = k => new { k.Id ,k.Name };
var serult = myData.Select(selector);
// .Select(p=> new {p.Name , p.Id}) etc.
This sample code is working
But;
Expression<Func<someClass, ???>> createSelector(string[] fields)
{
...
....
return ...
}
Expression<Func<someClass, ???>> selector = createSelector({"Name","Id"});
Is this possible?
This method when running create dynamic selector
This can be used to create the expression you need.
public static Expression<Func<T, TReturn>> CreateSelector<T, TReturn>(string fieldName)
where T : class
where TReturn : class
{
ParameterExpression p = Expression.Parameter(typeof(T), "t");
Expression body = Expression.Property(p, fieldName);
Expression conversion = Expression.Convert(body, typeof(object));
return Expression.Lambda<Func<T, TReturn>>(conversion, new ParameterExpression[] { p });
}
I have this :
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
}
public class City
{
public int Id { get; set; }
public string Name { get; set; }
public int ZipCode { get; set; }
}
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int? Age { get; set; }
public City City { get; set; }
public Company Company { get; set; }
}
I'd like a some case generate the predicate like this :
var result = listPerson.Where(x => x.Age == 10).ToList<>();
Or this :
var result = listPerson.Where( x => x.Company.Name == 1234).ToList();
Or this :
var result = listPerson.Where( x => x.City.ZipCode == "MyZipCode").ToList();
Or this :
var result = listPerson.Where( x => x.Company.Name == "MyCompanyName").ToList();
Then I created a "PredicateBuilder", that's work (I get the type, if nullable or not and I build the predicate) when I do this :
BuildPredicate<Person>("Age", 10); I get this : x => x.Age == 10
But I don't how manage when there is an nested property like this :
BuildPredicate<Person>("City.ZipCode", "MyZipCode");
I'd like get this : x => x.City.ZipCode == "MyZipCode"
Or this :
BuildPredicate<Person>("City.Name", "MyName");
I'd like get this : x => x.City.Name == "MyName"
Or this :
BuildPredicate<Person>("Company.Name", "MyCompanyName");
I'd like get this : x => x.Company.Name == "MyCompanyName"
(not intending to duplicate Jon - OP contacted me to provide an answer)
The following seems to work fine:
static Expression<Func<T,bool>> BuildPredicate<T>(string member, object value) {
var p = Expression.Parameter(typeof(T));
Expression body = p;
foreach (var subMember in member.Split('.')) {
body = Expression.PropertyOrField(body, subMember);
}
return Expression.Lambda<Func<T, bool>>(Expression.Equal(
body, Expression.Constant(value, body.Type)), p);
}
The only functional difference between that and Jon's answer is that it handles null slightly better, by telling Expression.Constant what the expected type is. As a demonstration of usage:
static void Main() {
var pred = BuildPredicate<Person>("City.Name", "MyCity");
var people = new[] {
new Person { City = new City { Name = "Somewhere Else"} },
new Person { City = new City { Name = "MyCity"} },
};
var person = people.AsQueryable().Single(pred);
}
You just need to split your expression by dots, and then iterate over it, using Expression.Property multiple times. Something like this:
string[] properties = path.Split('.');
var parameter = Expression.Parameter(typeof(T), "x");
var lhs = parameter;
foreach (var property in properties)
{
lhs = Expression.Property(lhs, property);
}
// I've assumed that the target is a string, given the question. If that's
// not the case, look at Marc's answer.
var rhs = Expression.Constant(targetValue, typeof(string));
var predicate = Expression.Equals(lhs, rhs);
var lambda = Expression.Lambda<Func<T, bool>>(predicate, parameter);
I'd like to dynamically create a MemberAcess Expression to a deeper level then 1 (recursively):
public class Job
{
public string Name { get; set; }
public int Salary { get; set; }
}
public class Employee
{
public string Name { get; set; }
public Job Job { get; set; }
}
And I want to dynamically create a list of MemberAccesExpressions for each property in Employee and each property in Employee's complex members, the outcome should be something like this:
MemberAccesExpression[] {
{ e => e.Name },
{ e => e.Job.Name },
{ e => e.Job.Name }
}
This is a pseudo code of what I got:
List list = new List();
public Expression<Func<TModel, dynamic>> CreateME<TModel>(TModel model)
{
var type = typeof (TModel);
var properties = type.GetProperties();
foreach (var prop in properties)
{
// I want to ignore collections
if (typeof(ICollection).IsAssignableFrom(prop.PropertyType)) continue;
// Recall for complex property
if (prop.PropertyType.Namespace != "System")
{
// CreateME(model, ) // THIS IS WHEN I DON'T KNOW WHAT TO DO
continue;
}
var param = Expression.Parameter(type, "x");
var memberAccess = Expression.PropertyOrField(param, prop.Name);
list.Add(Expression.Lambda<Func<TModel, dynamic>>(memberAccess, param));
}
}
How do I make this a recursive method?
I thought of adding an optional parameter named
(TModel model, Expression> baseMemberAccess = null)
and somehow concat the member expression to baseMemberAccess if it's not null.
P.S.
is there's a better way to determine if a Type is not atomic type then this
(prop.PropertyType.Namespace != "System")
? (not int,float,string,etc...)
Appreciate any help,
Adam
An edit for trying to lay it more simply:
If I want to create an expression tree of a member access to Employee.Name this is what I do:
var param = Expression.Parameter(type, "x");
var memberAccess = Expression.PropertyOrField(param, memberName);
return Expression.Lambda<Func<TModel, TMember>>(memberAccess, param);
What is the equivalent to this for a member access to Employee.Job.Salary ?
public IEnumerable<Expression<Func<TModel, dynamic>>> CreateME<TModel>()
{
var stack = new Stack<StackItem>();
var type = typeof(TModel);
var parameterExpression = Expression.Parameter(type, "x");
stack.Push(new StackItem(typeof(TModel), parameterExpression));
while (stack.Count > 0)
{
var currentItem = stack.Pop();
var properties = currentItem.PropertyType.GetProperties();
foreach (var property in properties)
{
if (IsComplexProperty(property))
stack.Push(new StackItem(property.PropertyType, Expression.PropertyOrField(currentItem.AccessChainExpression, property.Name)));
else
{
yield return GetSimplePropertyExpression<TModel>(parameterExpression, currentItem.AccessChainExpression, property.Name);
}
}
}
}
private static Expression<Func<TModel, dynamic>> GetSimplePropertyExpression<TModel>(ParameterExpression lhs, Expression accessChain, string propertyName)
{
var memberAccess = Expression.Convert(Expression.PropertyOrField(accessChain, propertyName), typeof(object));
return Expression.Lambda<Func<TModel, dynamic>>(memberAccess, lhs);
}
private static bool IsComplexProperty(PropertyInfo p)
{
return !typeof (ICollection).IsAssignableFrom(p.PropertyType) && p.PropertyType.Namespace != "System";
}
class StackItem
{
public StackItem(Type propertyType, Expression accessChainExpression)
{
PropertyType = propertyType;
AccessChainExpression = accessChainExpression;
}
public Type PropertyType { get; private set; }
public Expression AccessChainExpression { get; private set; }
}
Im sure it can be improved, but this should work.