Reflections, searching strings AND ints in a class - c#

Class DB_Class {
public int a { get; set; }
public long b { get; set; }
protected string c { get; set; }
protected DateTime F { get; set; }
}
Say there is a class like above, and I want to search in that class and return an IQueryable result because maybe I want to order it later.
I need a function like below---but the below function does NOT work with "integers" or "dateTime", only strings... (because of contains function). I need a more universal function that can take any type--kinda like Python does.
Maybe I need two templates???
public static IQueryable<T> WhereContains<T>(this IQueryable<T> query, string propertyName, string contains)
{
var parameter = Expression.Parameter(typeof(T), "type");
var propertyExpression = Expression.Property(parameter, propertyName);
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(contains, typeof(string));
var containsExpression = Expression.Call(propertyExpression, method, someValue);
return query.Where(Expression.Lambda<Func<T, bool>>(containsExpression, parameter));
}
I want to be able to call like this:
var table = WhereContains<typeoftable>(table, "a", "12");
// or
var table = WhereContains<typeoftable>(table, "c", "Jackso");
// or
var table = WhereContains<typeoftable>(table, "F", "11/23/201");

Related

EF Core dynamically filter from list

I am working on creating a generic dynamic filter on a front-end table. The idea is each column will have a filter, where you can select values of that column. The column names must be completely dynamic and selected values. We are using .NET 5 and EF Core 5.
The SQL query I want is:
SELECT *
FROM Users
WHERE externalId IN ('1234', '5678');
Models:
public class ColumnFilter
{
[Required]
public string Name { get; set; } // column name
public List<string> SelectedValues { get; set; } = new List<string>(); // values selected in this column
}
public class User {
public string Name { get; set; }
public string ExternalId { get; set; }
}
Simplified code:
//columnfilter really comes from API request
var columnFilter = new ColumnFilter{
Name = "ExternalId",
SelectedValues = new List<string>{ "1234", "5678" }
};
var queryable = _context.Users.AsNoTracking();
queryable = queryable.Where(f => columnFilter.SelectedValues.Contains(EF.Property<string>(f, columnFilter.Name)));
var values = await queryable.ToListAsync(); // 0 results
EF Core generates this query:
SELECT *
FROM [Users] AS [u]
WHERE 0 = 1
How do I get this to work dynamically?
It is a sample using string values:
var queryable = _context.Users.AsNoTracking();
queryable = queryable.FilterDynamic(columnFilter.Name, columnFilter.SelectedValues);
public static class QueryableExtensions
{
public static IQueryable<T> FilterDynamic<T>(this IQueryable<T> query, string fieldName, ICollection<string> values)
{
var param = Expression.Parameter(typeof(T), "e");
var prop = Expression.PropertyOrField(param, fieldName);
var body = Expression.Call(typeof(Enumerable), "Contains", new[] {typeof(string)},
Expression.Constant(values), prop);
var predicate = Expression.Lambda<Func<T, bool>>(body, param);
return query.Where(predicate);
}
}
The solution I had actually works. I had some other code involved that was causing the issue.

How to convent viewmodel to Expression<Func<T,bool>>?

Piggybacking off of a very similar question...
I need to generate an Expression from a ViewModel to pass as a search predicate for IQueryable.Where. I need to be able to include/exclude query parameters based on what is provided by the user. Example:
public class StoresFilter
{
public int[] Ids { get; set; }
[StringLength(150)]
public string Name { get; set; }
[StringLength(5)]
public string Abbreviation { get; set; }
[Display(Name = "Show all")]
public bool ShowAll { get; set; } = true;
public Expression<Func<Store, bool>> ToExpression()
{
List<Expression<Func<Store, bool>>> expressions = new List<Expression<Func<Store, bool>>>();
if (Ids != null && Ids.Length > 0)
{
expressions.Add(x => Ids.Contains(x.Id));
}
if (Name.HasValue())
{
expressions.Add(x => x.Name.Contains(Name));
}
if (Abbreviation.HasValue())
{
expressions.Add(x => x.Abbreviation.Contains(Abbreviation));
}
if (!ShowAll)
{
expressions.Add(x => x.Enabled == true);
}
if (expressions.Count == 0)
{
return x => true;
}
// how to combine list of expressions into composite expression???
return compositeExpression;
}
}
Is there a simple way to build a composite expression from a list of expressions? Or do I need to go through the process of manually building out the expression using ParameterExpression, Expression.AndAlso, ExpressionVisitor, etc?
You should not build and combine Expressions, but instead of this you should do it through IQuerable<Store> via .Where chain. Moreover, source.Expression will contain desired expression:
public IQueryable<Store> ApplyFilter(IQueryable<Store> source)
{
if (Ids != null && Ids.Length > 0)
source = source.Where(x => Ids.Contains(x.Id));
if (Name.HasValue())
source = source.Where(x => x.Name.Contains(Name));
if (Abbreviation.HasValue())
source = source.Where(x => x.Abbreviation.Contains(Abbreviation));
if (!ShowAll)
source = source.Where(x => x.Enabled == true);
//or return source.Expression as you wanted
return source;
}
Usage:
var filter = new StoresFilter { Name = "Market" };
var filteredStores = filter.ApplyFilter(context.Stores).ToList();
void Main()
{
var store = new Store
{
Id = 1,
Abbreviation = "ABC",
Enabled = true,
Name = "DEF"
};
var filter = new Filter<Store>
{
Ids = new HashSet<int>(new [] {1,2,3,4}),
Abbreviation = "GFABC",
Enabled = true,
Name = "SDEFGH",
ShowAll = false
}
var expression = filter.ToExpression(store);
var parameterType = Expression.Parameter(typeof(Store), "obj");
// Generate Func from the Expression Tree
Func<Store,bool> func = Expression.Lambda<Func<Store,bool>>(expression,parameterType).Compile();
}
public class Store
{
public int Id {get; set;}
public string Name {get; set;}
public string Abbreviation { get; set; }
public bool Enabled { get; set; }
}
public class Filter<T> where T : Store
{
public HashSet<int> Ids { get; set; }
public string Name { get; set; }
public string Abbreviation { get; set; }
public bool Enabled {get; set;}
public bool ShowAll { get; set; } = true;
public Expression ToExpression(T data)
{
var parameterType = Expression.Parameter(typeof(T), "obj");
var expressionList = new List<Expression>();
if (Ids != null && Ids.Count > 0)
{
MemberExpression idExpressionColumn = Expression.Property(parameterType, "Id");
ConstantExpression idConstantExpression = Expression.Constant(data.Id, typeof(int));
MethodInfo filtersMethodInfo = typeof(HashsetExtensions).GetMethod("Contains", new[] { typeof(HashSet<int>), typeof(int) });
var methodCallExpression = Expression.Call(null, filtersMethodInfo, idExpressionColumn, idConstantExpression);
expressionList.Add(methodCallExpression);
}
if (!string.IsNullOrEmpty(Name))
{
MemberExpression idExpressionColumn = Expression.Property(parameterType, "Name");
ConstantExpression idConstantExpression = Expression.Constant(data.Name, typeof(string));
MethodInfo filtersMethodInfo = typeof(StringExtensions).GetMethod("Contains", new[] { typeof(string), typeof(string) });
var methodCallExpression = Expression.Call(null, filtersMethodInfo, idExpressionColumn, idConstantExpression);
expressionList.Add(methodCallExpression);
}
if (!string.IsNullOrEmpty(Abbreviation))
{
MemberExpression idExpressionColumn = Expression.Property(parameterType, "Abbreviation");
ConstantExpression idConstantExpression = Expression.Constant(data.Abbreviation, typeof(string));
MethodInfo filtersMethodInfo = typeof(StringExtensions).GetMethod("Contains", new[] { typeof(string), typeof(string) });
var methodCallExpression = Expression.Call(null, filtersMethodInfo, idExpressionColumn, idConstantExpression);
expressionList.Add(methodCallExpression);
}
if (!ShowAll)
{
MemberExpression idExpressionColumn = Expression.Property(parameterType, "Enabled");
var binaryExpression = Expression.Equal(idExpressionColumn, Expression.Constant(true, typeof(bool)));
expressionList.Add(binaryExpression);
}
if (expressionList.Count == 0)
{
expressionList.Add(BinaryExpression.Constant(true));
}
// Aggregate List<Expression> data into single Expression
var returnExpression = expressionList.Skip(1).Aggregate(expressionList.First(), (expr1,expr2) => Expression.And(expr1,expr2));
return returnExpression;
// Generate Func<T,bool> - Expression.Lambda<Func<T,bool>>(returnExpression,parameterType).Compile();
}
}
public static class StringExtensions
{
public static bool Contains(this string source, string subString)
{
return source?.IndexOf(subString, StringComparison.OrdinalIgnoreCase) >= 0;
}
}
public static class HashsetExtensions
{
public static bool Contains(this HashSet<string> source, string subString)
{
return source.Contains(subString,StringComparer.OrdinalIgnoreCase);
}
}
How it works ?
Only in simple equality cases you can use BinaryExpression like Expression.Equal, Expression.GreaterThan, which is shown for the property like "ShowAll"
For other cases like string / Array / List Contains, you need extension method, which can take two types and provide the result. A separate Contains for string to make it case neutral. Also for collection Hashset has a better choice, it has O(1) time complexity, unlike O(N) for an array
We use MethodCallExpression to call the extension methods
Finally we aggreagte all the expressions, which can be compiled to create Func<T,bool>
In case you need something like x => true, then BinaryExpression.Constant(true) is sufficient
I have provided a Sample implementation using the Store class that you have defined

Building a dynamic expression tree to filter on a collection property 2

Maybe this is a duplicate thread, but I am going to try, because there is a tiny difference.
I am trying to build a dynamic expression to filter a collection property.
The code:
public class TestEntity
{
public int ID { get; set; }
public string Name { get; set; }
public IEnumerable<string> Values { get; set; }
}
public class TestFilter
{
public TestFilter()
{
var itens = new List<TestEntity>();
itens.Add(new TestEntity { ID = 1, Name = "Test1", Values = new List<string> { "V1", "V2" } });
itens.Add(new TestEntity { ID = 2, Name = "Test2", Values = new List<string> { "V6", "V3" } });
itens.Add(new TestEntity { ID = 3, Name = "Test3", Values = new List<string> { "V4", "V5" } });
itens.Add(new TestEntity { ID = 4, Name = "Test4", Values = new List<string> { "V2", "V3" } });
itens = itens.Where(e => e.Values.Any(c => c.Equals("V2"))).ToList();
**//Result IDs: 1, 4**
}
}
The filter above will give me IDs 1 and 4 as result.
I want to filter entities where exists a certain value in the collection "Values".
So far, I have tried this thread, but didnt realize how it can be done.
Any help would be apreciated.
So you are seeking for a method which given a collection property name and value will produce Where predicate like e => e.Collection.Any(c => c == value).
You can use the following extension method (hope the code is self explanatory):
public static class QueryableExtensions
{
public static IQueryable<T> WhereAnyEquals<T>(this IQueryable<T> source, string collectionName, object value)
{
var e = Expression.Parameter(typeof(T), "e");
var collection = Expression.PropertyOrField(e, collectionName);
var itemType = (collection.Type.IsIEnumerableT() ? collection.Type :
collection.Type.GetInterfaces().Single(IsIEnumerableT))
.GetGenericArguments()[0];
var c = Expression.Parameter(itemType, "c");
var itemPredicate = Expression.Lambda(
Expression.Equal(c, Expression.Constant(value)),
c);
var callAny = Expression.Call(
typeof(Enumerable), "Any", new Type[] { itemType },
collection, itemPredicate);
var predicate = Expression.Lambda<Func<T, bool>>(callAny, e);
return source.Where(predicate);
}
private static bool IsIEnumerableT(this Type type)
{
return type.IsInterface && type.IsConstructedGenericType &&
type.GetGenericTypeDefinition() == typeof(IEnumerable<>);
}
}
like this:
itens = itens.AsQueryable().WhereAnyEquals("Values", "V2").ToList();
If you step through the code, the variable predicate contains the expression you are asking for.

build an expression with multiple sorting

I am trying to build an expression for sorting, and i wrote code that sorts my list using one property.
But I need to sort it firstly by one property, secondly by another property and so on.
I mean I want to build an expression that will implement something like that: students.OrderBy(fistExpression.Compile()).ThenBy(secondImpression.Complie()).ThenBy(thirdExpression.Compile()).
So how to dynamically put that ThenBy methods?
Here is my code:
Type studentType = typeof(Student);
ParameterExpression studentParam = Expression.Parameter(studentType, "x");
MemberInfo ageProperty = studentType.GetProperty("Age");
MemberExpression valueInNameProperty =
Expression.MakeMemberAccess(studentParam, ageProperty);
Expression<Func<Student, int>> orderByExpression =
Expression<Func<Student, int>>.Lambda<Func<Student, int>>(valueInNameProperty, studentParam);
var sortedStudents = students.OrderBy(orderByExpression.Compile());
My solution:
public static Func<Student, object> BuildPredicate(string propertyName)
{
Type studentType = typeof(Student);
ParameterExpression studentParam = Expression.Parameter(studentType, "x");
MemberInfo ageProperty = studentType.GetProperty(propertyName);
MemberExpression valueInNameProperty = Expression.MakeMemberAccess(studentParam, ageProperty);
UnaryExpression expression = Expression.Convert(valueInNameProperty, typeof (object));
Expression<Func<Student, object>> orderByExpression = Expression.Lambda<Func<Student, object>>(expression, studentParam);
return orderByExpression.Compile();
}
in your expression making code is added casting to object.
That is how you can create a chain of ThenBy:
var sortedStudents = students.OrderBy(BuildPredicate("Age"));
foreach (var property in typeof(Student).GetProperties().Where(x => !String.Equals(x.Name, "Age")))
{
sortedStudents = sortedStudents.ThenBy(BuildPredicate(property.Name));
}
var result = sortedStudents.ToList();
Finally, Student sample class:
public class Student
{
public int Age { get; set; }
public string Name { get; set; }
}
Update:
Another approach is using attributes to mark properies from your Student to use them in OrderBy and ThenBy. Like:
public class Student
{
[UseInOrderBy]
public int Age { get; set; }
[UseInOrderBy(Order = 1)]
public string Name { get; set; }
}
[AttributeUsage(AttributeTargets.Property)]
internal class UseInOrderByAttribute : Attribute
{
public int Order { get; set; }
}
That is how you can build sorting chain using UseInOrderByAttribute:
Type studentType = typeof (Student);
var properties = studentType.GetProperties()
.Select(x => new { Property = x, OrderAttribute = x.GetCustomAttribute<UseInOrderByAttribute>() })
.Where(x => x.OrderAttribute != null)
.OrderBy(x => x.OrderAttribute.Order);
var orderByProperty = properties.FirstOrDefault(x => x.OrderAttribute.Order == 0);
if (orderByProperty == null)
throw new Exception("");
var sortedStudents = students.OrderBy(BuildPredicate(orderByProperty.Property.Name));
foreach (var property in properties.Where(x => x.Property.Name != orderByProperty.Property.Name))
{
sortedStudents = sortedStudents.ThenBy(BuildPredicate(property.Property.Name));
}
var result = sortedStudents.ToList();
Fix: BuildPredicate can be writen without dynamic. BuildPredicate sample code is changed.
I assume that you have private properties that you want to be able to sort.
If you for example have this class:
public class Student
{
public Student (int age, string name)
{
Age = age;
Name = name;
}
private string Name { get; set; }
public int Age { get; set; }
public override string ToString ()
{
return string.Format ("[Student: Age={0}, Name={1}]", Age, Name);
}
}
You can use the following method to build expressions that will get both public and private properties:
public static Func<TType, TResult> CreateExpression<TType, TResult>(string propertyName)
{
Type type = typeof(TType);
ParameterExpression parameterExpression = Expression.Parameter(type, propertyName);
MemberInfo property = type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
MemberExpression valueInProperty = Expression.MakeMemberAccess(parameterExpression, property);
return Expression.Lambda<Func<TType,TResult>>(valueInProperty, parameterExpression).Compile();
}
Example of usage:
var students = new [] {
new Student(20, "Ben"),
new Student(20, "Ceasar"),
new Student(20, "Adam"),
new Student(21, "Adam"),
};
var sortedStudents = students
.OrderBy(CreateExpression<Student, string>("Name"))
.ThenBy(CreateExpression<Student, int>("Age"));
sortedStudents.ToList().ForEach(student => Console.WriteLine(student));
/*
Prints:
[Student: Age=20, Name=Adam]
[Student: Age=21, Name=Adam]
[Student: Age=20, Name=Ben]
[Student: Age=20, Name=Ceasar]
*/

Better way to write this linq query Pt2

In response to this question:
Better way to write this linq query?
How would i build a dynamic query following the same pattern in that thread?
For example, the signature of the method changes to:
public List<PeopleSearchList> GetPeople(string filter, string searchType, string searchOption)
{
return a new List of type PeopleSearchList
}
So now i am not returning a single array of "Firstnames" etc i am returning a custom class.
The class would look like this:
public class PeopleSearchList
{
public String IdentityCode { get; set; }
public String Firstname { get; set; }
public String Surname { get; set; }
public Int32 LoanCount { get; set; }
public String Group { get; set; }
}
I worked it out.
Just thought i would post the solution for others to see.
public List<PeopleSearchList> GetPeople(string filter, string searchType, string searchOption)
{
IQueryable<Person> query = _context.People;
PropertyInfo property = typeof (Person).GetProperty(searchType);
MethodInfo method = typeof (string).GetMethod(searchOption, new[] {typeof (string)});
query = query.Where(WhereExpression(property, method, filter));
IQueryable<PeopleSearchList> resultQuery = query.Select(p => new PeopleSearchList
{
Firstname = p.Firstname,
Group = p.Level.Year,
IdentityCode = p.IdentityCode,
LoanCount = p.Loans.Count(),
Surname = p.Surname
}
).OrderBy(p => p.Surname);
return resultQuery.ToList();
}
Expression<Func<Person, bool>> WhereExpression(PropertyInfo property, MethodInfo method, string filter)
{
var param = Expression.Parameter(typeof(Person), "o");
var propExpr = Expression.Property(param, property);
var methodExpr = Expression.Call(propExpr, method, Expression.Constant(filter));
return Expression.Lambda<Func<Person, bool>>(methodExpr, param);
}

Categories

Resources