I have this program which I extend the LINQ Expression to get result using any property name and value as arguments. My extension is working fine for one property only however I have a need that I will look up and pass two or more properties as filters. Can anyone help me extend my LINQ? Below are my code implementations. I used Console Application.
Based on my sample code for the list of users I just call
GetByPropertyName("PropertyName", "Value");
For example
var user = GetByPropertyName("FirstName", "James");
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static List<User> _users = new List<User>();
static void Main(string[] args)
{
var users = new List<User> {
new User { FirstName = "John", MiddleName = "Hall", LastName = "Long", Email="john.long#gmail.com"},
new User { FirstName = "John", MiddleName = "Wine", LastName = "Crawford", Email="john.crawford#gmail.com" },
new User { FirstName = "James", MiddleName = "Cage", LastName = "Hall", Email="james.hall#hotmail.com" },
new User { FirstName = "Larry", MiddleName = "Wine", LastName = "Crawford", Email="larry.crawford#gmail.com" },
new User { FirstName = "Jennifer", MiddleName = "Wine", LastName = "Long", Email="jennifer.long#gmail.com"}
};
//works okay for one property name
_users = users;
var user = GetByPropertyName("FirstName", "James");
//works okay for one property name
_users = users;
var user1 = GetByPropertyName("Email", "james.hall#hotmail.com");
//NEED HELP
//For GetByPropertyNames two or more properties
var filters = new Dictionary<object, object>();
filters.Add("FirstName", "John");
filters.Add("Email", "john.long#gmail.com");
var user2 = GetByPropertyNames(filters);
}
public static User GetByPropertyName(object propertyName, object value)
{
var result = _users.AsQueryable().Where(propertyName.ToString(), value).FirstOrDefault();
return result;
}
public static User GetByPropertyNames(Dictionary<object, object> filters)
{
var result = _users.AsQueryable().Where(filters).FirstOrDefault();
return null;
}
}
public class User
{
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
}
Extensions.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace ConsoleApplication1
{
public static class Extensions {
public static IQueryable<T> Where<T>(this IQueryable<T> source, string propertyName, object value) {
return (IQueryable<T>)Where((IQueryable)source, propertyName, value);
}
public static IQueryable Where(this IQueryable source, string propertyName, object value) {
var x = Expression.Parameter(source.ElementType, "x");
var selector = Expression.Lambda(
Expression.Equal(
Expression.PropertyOrField(x, propertyName),
Expression.Constant(value)
), x
);
return source.Provider.CreateQuery(
Expression.Call(typeof(Queryable), "Where", new Type[] { source.ElementType }, source.Expression, selector)
);
}
//NEED HELP
//TO DO
public static IQueryable<T> Where<T>(this IQueryable<T> source, Dictionary<object, object> filters)
{
return (IQueryable<T>)Where((IQueryable)source, filters);
}
public static IQueryable Where(this IQueryable source, Dictionary<object, object> filters)
{
var x = Expression.Parameter(source.ElementType, "x");
//NEED HELP
//expression for _users.FirstOrDefault(x=>x.Filter1==Filter1Value && x.Filter2==Filter2Value && so on and so fort depends on how many filters are passed as arguments);
//var selector = Expression.Lambda(
// Expression.Equal(
// Expression.PropertyOrField(x, propertyName),
// Expression.Constant(value)
// ), x
//);
//return source.Provider.CreateQuery(
// Expression.Call(typeof(Queryable), "Where", new Type[] { source.ElementType }, source.Expression, selector)
//);
//remove line below
return null;
}
}
}
Drop your IQueryable Where(this IQueryable source, Dictionary<object, object> filters) and update your generic version as follows:
public static IQueryable<T> Where<T>(this IQueryable<T> source, Dictionary<object, object> filters)
{
foreach (var kv in filters)
{
source = source.Where(kv.Key.ToString(), kv.Value);
}
return source;
}
Adding multiple Where() statements implies AND logic. If you wanted OR logic that would be more painful.
Related
Using Microsoft.CodeAnalysis.CSharp.Scripting I have created a generic method to turn a string into a predicate:
public static Func<T, bool> CreatePredicate<T>(string command)
{
var options = ScriptOptions.Default.AddReferences(typeof(T).Assembly);
Func<T, bool> predicate = CSharpScript.EvaluateAsync<Func<T, bool>>(command, options).Result;
return predicate;
}
The problem is that this is very slow. It takes 3-4 seconds to generate the predicate.
Is there a way to turn a string into a predicate faster using Roslyn? (I am aware of alternative of manually creating expression using Expression class, this question is specifically at doing this with Roslyn)
Here is stadalone example:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
namespace DynamicLambda
{
class Program
{
static void Main(string[] args)
{
var albums = new List<Album>
{
new Album { Quantity = 10, Artist = "Betontod", Title = "Revolution" },
new Album { Quantity = 50, Artist = "The Dangerous Summer", Title = "The Dangerous Summer" },
new Album { Quantity = 200, Artist = "Depeche Mode", Title = "Spirit" },
};
var albumFilter = "album => album.Quantity > 20 && album.Quantity < 200";
var sw = new Stopwatch();
sw.Start();
var predicate = CreatePredicate<Album>(albumFilter);
sw.Stop();
var filteredAlbums = albums.Where(predicate).ToList();
}
public static Func<T, bool> CreatePredicate<T>(string command)
{
var options = ScriptOptions.Default.AddReferences(typeof(T).Assembly);
Func<T, bool> predicate = CSharpScript.EvaluateAsync<Func<T, bool>>(command, options).Result;
return predicate;
}
}
public class Album
{
public int Quantity { get; set; }
public string Title { get; set; }
public string Artist { get; set; }
}
}
I really don't like to hard code the name of properties of my models. So I came up with this code so far. My code is working fine and it does exactly what I want but in an ugly way. I'm pretty sure it will be problematic soon. So any help to improve it and make it work in the right way is appreciated. I'm looking to fine better way to extract selected property names without converting expression body to string. Any change to any part of this class is fine with me. Even changing usage as long as I don't hard code my property names.
What is the better way to extract selected properties name of a model?
Here is my code:
public class Selector<T> : IDisposable
{
Dictionary<string, Func<T, object>> Selectors = new Dictionary<string, Func<T, object>>();
public Selector(params Expression<Func<T, object>>[] Selector)
{
foreach (var select in Selector)
{
//string MemberName = CleanNamesUp(select.Body.ToString());
//Func<T, object> NewSelector = select.Compile();
#region Ugly Part 1
Selectors.Add(CleanNamesUp(select.Body.ToString()), select.Compile());
#endregion
}
}
#region I am Doing This So I can Use Using(var sl = new Selector<T>())
public void Dispose()
{
Selectors.Clear();
Selectors = null;
}
#endregion
#region Ugly Part 2
private string CleanNamesUp(string nameStr)
{
string name = nameStr.Split('.')[1];
if (name.Contains(","))
{
name = name.Split(',')[0];
}
return name;
}
#endregion
public Dictionary<string, object> GetFields(T Item)
{
Dictionary<string,object> SetFieldList = new Dictionary<string, object>();
foreach(var select in Selectors)
{
SetFieldList.Add( select.Key , select.Value(Item));
}
return SetFieldList;
}
public List<Dictionary<string, object>> GetFields(IEnumerable<T> Items)
{
List<Dictionary<string, object>> SetFieldListMain = new List<Dictionary<string, object>>();
foreach (var item in Items)
{
Dictionary<string, object> SetFieldList = new Dictionary<string, object>();
foreach (var select in Selectors)
{
SetFieldList.Add(select.Key, select.Value(item));
}
SetFieldListMain.Add( SetFieldList);
}
return SetFieldListMain;
}
internal List<string> GetKeys()
{
return new List<string>(this.Selectors.Keys);
}
}
This is my model:
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public bool IsEnabled { get; set; }
public bool IsLocked { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime LockedAt { get; set; }
}
And I am using it like this:
User user1 = new User();
user1.Email = "testDev#gmail.com";
user1.UserName = "dora";
user1.Password = "123456";
var UpObject = new Selector<User>( x => x.UserName, x => x.Password, x => x.Email, x => x.IsEnabled );
Dictionary<string,object> result = UpObject.GetFields(user1);
You can avoid parsing the expressions as string if you instead parse them as System.Linq.Expressions.
Full code sample follows, but not exactly for your code, I used DateTime instead of the generic T, adapting should just be find&replace:
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace ExprTest
{
class Program
{
static void Main(string[] args)
{
#region Usage
Expression<Func<DateTime, object>> propertySelector = x => x.Day;
Expression<Func<DateTime, object>> methodSelector = x => x.AddDays(1.5);
Expression[] inputSelectors = new Expression[] { propertySelector, methodSelector };
#endregion
//These are your final Selectors
Dictionary<string, Func<DateTime, object>> outputSelectors = new Dictionary<string, Func<DateTime, object>>();
//This would be in your Selector<T> constructor, replace DateTime with T.
//Instead of CleanNamesUp you would decide which part to use by extracting the appropriate Expression argument's Name.
foreach (Expression<Func<DateTime, object>> selectorLambda in inputSelectors)
{
Expression selectorExpression = selectorLambda.Body;
string name = null;
while (string.IsNullOrEmpty(name))
{
switch (selectorExpression)
{
#region Refine expression
//Necessary for value typed arguments, which get boxed by Convert(theStruct, object)
case UnaryExpression unary:
selectorExpression = unary.Operand;
break;
//add other required expression extractions
#endregion
#region Select expression key/name
case MemberExpression fieldOrProperty:
name = fieldOrProperty.Member.Name;
break;
case MethodCallExpression methodCall:
name = methodCall.Method.Name;
break;
//add other supported expressions
#endregion
}
}
outputSelectors.Add(name, selectorLambda.Compile());
}
//Set a breakpoint here to explore the outputSelectors
}
}
}
There could be a library for this, but i don't know about any, except PredicateBuilder for when you need to unify lambda arguments into one lambda expression.
I think maybe you forgot an important keyword 'nameof'. With the keyword, the code will be like this:
class User
{
public string Name { get; set; }
public string Address { get; set; }
public string Tel { get; set; }
}
static Dictionary<string, object> GetFieldsOf<T>(T item, params string[] args)
{
var properties = args.Select(property => typeof(T).GetProperty(property));
return properties.ToDictionary(property => property.Name, property => property.GetValue(item));
}
static void Main(string[] args)
{
var user = new User { Name = "Abel", Address = "Unknown", Tel = "XXX-XXX" };
var result = GetFieldsOf(user, nameof(User.Name), nameof(User.Address));
}
This code will result in some performance problems caused by reflection. But fortunately, you can avoid these by emitting a small segement of code.
//MSIL
ldarg.0
call Property.GetMethod
ret
And replace it with proerpty.GetValue. These code can be generated and cached per type, which is still worthwhile.
I have two expressions like this:
Expression<Func<T, T> exp1 = x => new T { Id = 1 , Name = "string"}
Expression<Func<T, T> exp2 = x => new T { Age = 21 }
How can I merge them?
Result:
Expression<Func<T, T> exp3 = x => new T { Id = 1 , Name = "string" , Age = 21}
T is : IEntityBaseRepository<T> where T : class, IEntityBase, new()
IEntityBase:
public interface IEntityBase
{
string Id { get; set; }
string Name { get; set; }
int Age { get; set; }
}
public virtual async Task UpdateAsync(string id, Expression<Func<T, T>> updateFactory)
{
Expression<Func<T, T>> updateFactory2 = s => new T { age = 21 };
//Expression<Func<T, T>> updateFactoryFinal = updateFactory + updateFactory2;
await _context.Set<T>().Where(w => w.Id == id).UpdateAsync(updateFactoryFinal);
}
Because you never use Func argument you can simplify your code and use Func<T>. Next method will merge two (or more) MemberInitExpression into one:
public static Expression<Func<T>> MergeExpressions<T>(params Expression<Func<T>>[] expressions)
where T : new()
{
var allBindings = new List<MemberBinding>();
foreach (var expression in expressions)
{
var bindings = ((MemberInitExpression) expression.Body).Bindings;
allBindings.AddRange(bindings);
}
var body = Expression.MemberInit(Expression.New(typeof(T)), allBindings);
return Expression.Lambda<Func<T>>(body);
}
You can check it online here
Following is my code to get a Expression<Func<T,bool>>, which helps in providing the Func<T,bool>, for a list filtering, now for that I need to create a MemberExpression as follows (In code):
MemberExpression memberExpressionColumn = Expression.Property(parameterType,"X");
Where X is the name of property meant for filtering, so when I am applying it on a List<Person>, it is easy to replace X with property Name, but when I use the List<string> as source, then how to create MemberExpression, which is leading to Exception
void Main()
{
List<Person> personList = new List<Person>()
{
new Person{ Name = "Shekhar", Age = 31},
new Person{ Name = "Sandip", Age = 32},
new Person{ Name = "Pramod", Age = 32},
new Person{ Name = "Kunal", Age = 33}
};
var personNameList = personList.Select(p => p.Name).ToList();
var personNameHashset = new HashSet<string>(personNameList);
var nameList = new List<string>() { "Kunal", "Pramod", "Mrinal" };
var finalExpression = personNameHashset.EqualExpression<string>("Name");
var finalFunc = finalExpression.Compile();
var result = nameList.Where(finalFunc);
result.Dump();
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
public static class ExpressionTreesExtension
{
public static Expression<Func<T, bool>> EqualExpression<T>(this HashSet<string> nameHashSet, string columnName)
{
var expressionList = new List<Expression>();
ParameterExpression parameterType = Expression.Parameter(typeof(T), "obj");
// Exception Here - How to create MemberExpression for primitive type list List<string>
MemberExpression memberExpressionColumn = Expression.Property(parameterType,columnName);
var containsMethodInfo = typeof(StringListExtensions).GetMethod("Contains", new[] { typeof(string), typeof(HashSet<string>) });
ConstantExpression constant = Expression.Constant(nameHashSet, typeof(HashSet<string>));
var resultExpression = Expression.Call(null, containsMethodInfo, memberExpressionColumn, constant);
return Expression.Lambda<Func<T, bool>>(resultExpression, parameterType);
}
}
public static class StringListExtensions
{
/// <summary>
/// String Extension - Contains (Substring)
/// </summary>
/// <param name="source"></param>
/// <param name="subString"></param>
/// <returns></returns>
public static bool Contains(this string name, HashSet<string> nameHashSet)
{
return nameHashSet.Contains(name);
}
}
As I understand you need to get result like this:
var result = nameList.Where(obj => personNameHashset.Contains(obj));
Since you don't need to get Name property from the string you shouldn't use memberExpressionColumn and you can call containsMethodInfo with ParameterExpression parameterType. You can simplify your method to get Expression<Func<T, bool>> like this
public static Expression<Func<T, bool>> EqualExpression<T>(this HashSet<string> nameHashSet)
{
ParameterExpression parameterType = Expression.Parameter(typeof(T), "obj");
var containsMethodInfo = typeof(StringListExtensions).GetMethod("Contains", new[] { typeof(string), typeof(HashSet<string>) });
ConstantExpression constant = Expression.Constant(nameHashSet, typeof(HashSet<string>));
var resultExpression = Expression.Call(null, containsMethodInfo, parameterType, constant);
return Expression.Lambda<Func<T, bool>>(resultExpression, parameterType);
}
and call it:
var finalExpression = personNameHashset.EqualExpression<string>();
var finalFunc = finalExpression.Compile();
var result = nameList.Where(finalFunc);
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]
*/