Linq IQueryable Generic Filter - c#

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

How to convert Lambda Expressions from diferent domains?

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

C#: Search a list of defined queryable fields, different per model, with linq through generics (using entity framework)

*****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.

dynamically build select clause linq

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 });
}

Create predicate with nested classes with Expression

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);

Creating MemberAcces with ExpressionTree to a complex type

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.

Categories