Build Dynamic Expression For Linq Where using PropertyInfo and a value - c#

As the title states, I would like to build a dynamic expression using propertyInfo and a value.
Currently i have something like this
public static IQueryable<T> WhereValueEquals<T>(this IQueryable<T> q, FieldFor fieldFor, string fieldValue)
where T : ModuleData
{
switch (fieldFor)
{
case FieldFor.Name:
return q.Where(e => e.Name == fieldValue);
case FieldFor.Reference:
return q.Where(e => e.Reference == fieldValue);
case FieldFor.Text0:
return q.Where(e => e.Reference == fieldValue);
default:
return q;
}
}
Now the case statement is only going to get longer and longer, but this was fine during early stages of development.
Now, using the FieldFor enumeration, i can get the propertyInfo using an extension i already wrote. So how can i return an IQueryable from WhereValueEquals using the string value passed in and a propertyInfo
So far i have
public static IQueryable<T> WhereValueEquals<T>(this IQueryable<T> q, FieldFor fieldFor, string fieldValue)
where T : ModuleData
{
PropertyInfo propertyInfo = typeof(T).GetFieldProperties().GetPropertyByFieldFor(fieldFor);
//return q.Where(expression);
}

At some point in the time I had to do something similar, and there are a ton of code on Stackoverflow that show you how to build an expression builder. Well this is my POC, hope it helps you
void Main()
{
var ops = new List<Ops>
{
new Ops
{
//OperandType = typeof(string),
OpType=OpType.Equals,
OperandName = "Name",
ValueToCompare = "MM"
},
new Ops
{
//OperandType = typeof(int),
OpType=OpType.Equals,
OperandName = "ID",
ValueToCompare = 1
},
};
var testClasses = new List<TestClass>
{
new TestClass { ID =1, Name = "MM", Date = new DateTime(2014,12,1)},
new TestClass { ID =2, Name = "BB", Date = new DateTime(2014,12,2)}
};
var funct = ExpressionBuilder.BuildExpressions<TestClass>(ops);
foreach(var item in testClasses.Where(funct))
{
Console.WriteLine("ID " +item.ID);
Console.WriteLine("Name " +item.Name);
Console.WriteLine("Date" + item.Date);
}
}
// Define other methods and classes here
public enum OpType
{
Equals
}
public class Ops
{
//public Type OperandType {get; set;}
public OpType OpType {get; set;}
public string OperandName {get;set;}
public object ValueToCompare {get;set;}
}
public class TestClass
{
public int ID {get;set;}
public string Name {get; set;}
public DateTime Date {get;set;}
}
public class ExpressionBuilder
{
public static Func<T,bool> BuildExpressions<T>( List<Ops> opList)
{
Expression currentExpression= null;
var parameter = Expression.Parameter(typeof(T), "prop");
for(int i =0; i< opList.Count; i++)
{
var op = opList[i];
Expression innerExpression = null;
switch(op.OpType)
{
case OpType.Equals :
{
var innerParameter = Expression.Property(parameter,op.OperandName);
var ConstExpression = Expression.Constant(op.ValueToCompare);
innerExpression = Expression.Equal(innerParameter, ConstExpression);
break;
}
}
if (i >0)
{
currentExpression = Expression.And(currentExpression, innerExpression);
}
else
{
currentExpression = innerExpression;
}
}
var lambdaExpression = Expression.Lambda<Func<T,bool>>(currentExpression, new []{parameter});
Console.WriteLine(lambdaExpression);
return lambdaExpression.Compile() ;
}
}

Related

C#- Filter data based on child class or using string query

Hi I have a scenario to filter the data based sub-object field please help me. From controller as query I pass Expression String.
class MasterDocument
{
private Id;
public ICollection<SubDocument> documents { get; set; }
}
class SubDocument
{
private Id;
public int Age { get; set; }
}
var filterQuery = "documents.Age == 25";
var filteredResult = MasterDocument.Where(filterQuery).ToList();
to filter the Data 
how to create Expression from string to filter data from Substructure.
Well, that's quite complicated topic, but i will first give code example and later focus on caveats:
I would follow approach and define it as another extension method:
using System.Linq.Expressions;
namespace ConsoleApp2;
public static class WhereExtensions
{
public static IEnumerable<T> Where<T>(
this IEnumerable<T> collection,
string filterExpression)
{
// Most probably you'd like to have here more sophisticated validations.
var itemsToCompare = filterExpression.Split("==")
.Select(x => x.Trim())
.ToArray();
if (itemsToCompare.Length != 2)
{
throw new InvalidOperationException();
}
var source = Expression.Parameter(typeof(T));
var property = itemsToCompare[0];
var valueToCompareAgainst = itemsToCompare[1];
var memberExpr = source.GetMemberExpression(property);
var comparisonExpr = Expression.Equal(
Expression.Call(memberExpr, typeof(object).GetMethod("ToString")),
Expression.Constant(valueToCompareAgainst)
);
var predicate = Expression.Lambda<Func<T, bool>>(comparisonExpr, source);
return collection.Where(predicate.Compile());
}
public static MemberExpression GetMemberExpression(
this ParameterExpression parameter,
string memberExpression)
{
var properties = memberExpression.Split('.');
if (!properties.Any())
{
throw new InvalidOperationException();
}
var memberExpr = Expression.PropertyOrField(parameter, properties[0]);
foreach (var property in properties.Skip(1))
{
memberExpr = Expression.PropertyOrField(memberExpr, property);
}
return memberExpr;
}
}
and the usage would be:
using ConsoleApp2;
var example = new[]
{
new TestClass() { Id = 1, Description = "a" },
new TestClass() { Id = 2, Description = "a" },
new TestClass() { Id = 3, Description = "b" },
new TestClass() { Id = 4, Description = "b" },
};
var result1 = example.Where("Id == 1").ToList();
var result2 = example.Where("Description == b").ToList();
Console.WriteLine("Items with Id == 1");
result1.ForEach(x => Console.WriteLine($"Id: {x.Id} , Descr: {x.Description}"));
Console.WriteLine("Items with Description == b");
result2.ForEach(x => Console.WriteLine($"Id: {x.Id} , Descr: {x.Description}"));
class TestClass
{
public int Id { get; set; }
public string Description { get; set; }
}
This codes returns:
NOW, THE CAVEATS
It's very tricky to cast value to compare against to an arbitrary type T, that's why i reversed the problem, and I call "ToString" on whetever member we want to compare
Expression.Call(memberExpr, typeof(object).GetMethod("ToString"))
But this also could have it's own issues, as often "ToString" returns default tpye name. But works well with integers and simple value types.

Creating dynamic Expression Tree for selected search criteria

I have an Expression Tree to create a dynamic where clause based on the criteria a user selects on a checkbox.
Eg: - User wants to search for: "test"
User selects
1. Prop1
2. Prop2
for an Object
MyDBObject
The search query will look like
dbRecords.Where(r=> r.Prop1.Contains("test") || r.Prop2.Contains("test"))
The reason to use an Expression Tree is so that it can be used for any unknown number of properties of an unknown object.
I almost have it working, but I get Argument Expression is not valid
Also how does one initialize an empty boolean expression other than using
"something that evaluates to -- true/false" ?
I've only read about them for a few hours by now so maybe there's something I didn't see yet.
public static Expression<Func<T, bool>> CreatePredicateFromCrtieriaAndSearchTerm<T>(List<string> checkedCriteria, string searchTerm)
{
// sample checked records
checkedCriteria = new[]
{
new { Name = "Prop1", DisplayValue = "Checkbox value 1" },
new { Name = "Prop2", DisplayValue = "Checkbox value 2" }
}
.Select(x => x.Name).ToList();
var param = Expression.Parameter(typeof(T), "record");
Expression oneEqualsOne = Expression.Equal(Expression.Constant(1), Expression.Constant(1));
// Creates (record => (1=1) AND ...)
Expression<Func<T, bool>> finalExpression = Expression.Lambda<Func<T, bool>>(oneEqualsOne, param);
Console.WriteLine(finalExpression);
try
{
// Iterate through properties, find selected props and create
// (record.SelectedProp1.Contains("searchTerm") || record.SelectedProp2.Contains("searchTerm") ... )
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(T));
List<Expression> matchExpressions = new List<Expression>();
for (int i = 0; i < props.Count; i++)
{
PropertyDescriptor prop = props[i];
for (int j = 0; j < checkedCriteria.Count; j++)
{
if (prop.Name == checkedCriteria[j])
{
// add to where expression
Expression left = Expression.Property(param, prop.Name);
MethodInfo contains = typeof(string).GetMethod("Contains", new[] { typeof(string) });
Expression right = Expression.Constant(searchTerm, searchTerm.GetType());
Expression matchExpression = Expression.Call(left, contains, right);
matchExpressions.Add(matchExpression);
}
}
}
// Creates (1=0 OR ... OR ...)
Expression currentPredicateBody = Expression.Equal(Expression.Constant(1), Expression.Constant(0));
foreach (var matchExpression in matchExpressions)
{
currentPredicateBody = Expression.MakeBinary(ExpressionType.OrElse, matchExpression, currentPredicateBody);
Console.WriteLine(currentPredicateBody);
}
// ( (1=0) || record.SelectedProp1.Contains("searchTerm") || record.SelectedProp2.Contains("searchTerm") )
if (matchExpressions.Count > 0)
{
oneEqualsOne = Expression.AndAlso(oneEqualsOne, currentPredicateBody);
Console.WriteLine(oneEqualsOne);
}
// Full expression:
// ( record => (1=1) AND ( (1=0) || record.SelectedProp1.Contains("searchTerm") || record.SelectedProp2.Contains("searchTerm") ))
finalExpression = Expression.Lambda<Func<T, bool>>(oneEqualsOne, new ParameterExpression[] { param });
Console.WriteLine(finalExpression);
}
catch (Exception ex)
{
throw new Exception(string.Format(#"Error occurred creating where predicate from checked criteria: {0}", ex.Message));
}
return finalExpression;
}
internal class MyDBObject
{
public int Id { get; set; }
public string Prop1 { get; set; }
public string Prop2 { get; set; }
public string Prop3 { get; set; }
public string Prop4 { get; set; }
public string Prop5 { get; set; }
public string Prop11 { get; set; }
public string Prop12 { get; set; }
public string Prop13 { get; set; }
public string Prop14 { get; set; }
public string Prop15 { get; set; }
public string Prop21 { get; set; }
public string Prop22 { get; set; }
public string Prop23 { get; set; }
public string Prop24 { get; set; }
public string Prop25 { get; set; }
}
public static void Main(string[] args)
{
List<MyDBObject> dbRecords = new List<MyDBObject>
{
new MyDBObject { Id = 1, Prop2 = "O1_P2", Prop3 = "O1_P3", Prop12 = "O1_P12", Prop15 = "O1_P15", Prop24 = "O1_P24", Prop25 = "O1_P25" },
new MyDBObject { Id = 2, Prop15 = "O2_P15", Prop21 = "test", Prop22 = "O2_P22", Prop23 = "O2_P23", Prop24 = "O2_P24", Prop25 = "O2_P25" },
new MyDBObject { Id = 3, Prop21 = "O3_P21", Prop22 = "O3_P22", Prop23 = "O3_P23", Prop24 = "test", Prop25 = "O3_P25" }
};
try
{
var predicate = CreatePredicateFromCrtieriaAndSearchTerm<MyDBObject>(null, "test");
var query = dbRecords.AsQueryable().Provider.CreateQuery<MyObject>(predicate);
List<MyObject> results = query.ToList();
foreach (var rs in results)
{
Console.WriteLine("Id: " + rs.Id);
}
}
catch (Exception ex)
{
Console.WriteLine("Error->> " + ex.Message);
}
}
Try this code:
public static Expression<Func<T, bool>> CreatePredicate<T>(List<string> propsToSearch,
string valueToSearch)
{
var parameter = Expression.Parameter(typeof(T), "record");
// filtering is not required
if (!propsToSearch.Any() || string.IsNullOrEmpty(valueToSearch))
return Expression.Lambda<Func<T, bool>>(Expression.Constant(true), parameter);
var props = typeof(T).GetProperties()
.Select(p => p.Name)
.Intersect(propsToSearch.Distinct());
var containsMethod = typeof(string).GetMethod("Contains");
var body = props
.Select(p => Expression.PropertyOrField(parameter, p))
.Aggregate((Expression) Expression.Constant(false),
(c, n) => Expression.OrElse(c,
Expression.Call(n, containsMethod, Expression.Constant(valueToSearch)))
);
var lambda = Expression.Lambda<Func<T, bool>>(body, parameter);
return lambda;
}
It return record => true if there is no properties to search or search patern is empty. QueryProvider can be smart enough to not generate sql where in this case.
Update: I created a demo (it's not working because of security restriction of dotNetFiddle, but localy works fine)

Sort List of parent class by property of subclass using LINQ

I have a class with the following structure
public class DisplayItem
{
public string Id { get; set; }
public SortFields SortFields { get; set; }
}
with a sub class
public class SortFields
{
public string ProductNumber { get; set; }
public DateTime DateOfUpload { get; set; }
}
So the idea is to sort a List based on the values in the SortFields class properties.
The most basic solution i can think of is to do something like this
public IEnumerable<DisplayItem> Sort(IEnumerable<DisplayItem> source, string sortBy, SortDirection sortDirection)
{
switch (sortBy)
{
case "Product Number":
switch (sortDirection)
{
case SortDirection.Ascending:
return source.OrderBy(x => x.SortFields.ProductNumber);
case SortDirection.Descending:
return source.OrderByDescending(x => x.SortFields.ProductNumber);
}
case "Uploaded Date":
{
//--do--
}
}
}
Ideally i would like to be able to pass the sortBy as a parameter in the orderby method and while i did find a couple of solutions on the internet , they dont seem to be able to sort the list of the base class based on subclass property.
is there a way that we can pass the sub class property as a parameter and be able to sort the list the parent class?
Store your sort by properties and lambda in a dictionary Dictionary<string,Func<DisplayItem,string>> dct and pass into the method:
public IEnumerable<DisplayItem> Sort(IEnumerable<DisplayItem> source, string sortBy, Dictionary<string,Func<DisplayItem,string>> dct, SortDirection sortDirection)
{
switch (sortDirection)
{
case SortDirection.Ascending:
return source.OrderBy(x => dct[sortBy]);
case SortDirection.Descending:
return source.OrderByDescending(x => dct[sortBy]);
default:
return null;
}
}
So you would store your lambda like:
dct["ProductNumber"] = x => x.SortFields.ProductNumber;
You can you this code,
that get a function to choose on which field to sort:
For example, code will be like this:
Sort(list, (x) => x.SortFields.ProductNumber, true);
public static IEnumerable<T> Sort(IEnumerable<T> source, Func<T,string> sortBy, bool isUp) where T : new()
{
if(isUp)
{
return source.OrderBy(a => sortBy.Invoke(a));
}
else
{
return source.OrderByDescending(a => sortBy.Invoke(a));
}
}
this is may be helpful you.
private static readonly MethodInfo OrderByMethod = typeof(Queryable).GetMethods().Single(method => method.Name == "OrderBy" && method.GetParameters().Length == 2);
private static readonly MethodInfo OrderByDescendingMethod = typeof(Queryable).GetMethods().Single(method => method.Name == "OrderByDescending" && method.GetParameters().Length == 2);
static IQueryable<T> OrderByQuery<T>(IQueryable<T> source, string propertyName, SortDirection direction)
{
string[] memberTree = propertyName.Split('.');
Expression ex = null;
Type currentType = typeof(T);
ParameterExpression parameterExpression = Expression.Parameter(currentType);
for (int i = 0; i < memberTree.Length; i++)
{
ex = Expression.Property(ex != null ? ex : parameterExpression, memberTree[i]);
}
LambdaExpression lambda = Expression.Lambda(ex, parameterExpression);
MethodInfo genericMethod =
direction == SortDirection.Descending
? OrderByDescendingMethod.MakeGenericMethod(currentType, ex.Type)
: OrderByMethod.MakeGenericMethod(currentType, ex.Type);
object ret = genericMethod.Invoke(null, new object[] { source, lambda });
return (IQueryable<T>)ret;
}
using..
public class A { public B MemberB { get; set; }}
public class B { public string Name { get; set; }}
List<A> listt = new List<A>{
new A{ MemberB = new B{Name ="a"}},
new A{ MemberB = new B{Name ="b"}},
new A{ MemberB = new B{Name ="u"}},
new A{ MemberB = new B{Name ="l"}},
new A{ MemberB = new B{Name ="i"}}
};
var rr = OrderByQuery(listt.AsQueryable(), "MemberB.Name", SortDirection.Ascending).ToList();

Comparing two objects from the same type

I'm trying to get all the fields that had been modified, comparing two objects from the same type.
For example:
public class Order
{
public int OrderNumber {get;set;}
public DateTime OrderDate {get;set};
public string Something {get;set};
}
Then, I save a new Order:
Order order1 = new Order;
order1.OrderNumber = 1;
order1.OrderDate = DateTime.Now;
order1.Something = string.Empty;
Save(order1)
After that, somebody tries to change some information from this order and I'm trying to find out the best way to get all the fields that were changed and save into a Log.
This must work for any type of two objects;
Should be a method like
public something ReturnFields(TObject objectSaved, TObject objectChanged)
Can anyone help me?
You can use reflection to get the properties on an object, and build a series of expressions to compare each property. That way you can execute them, and for those that aren't equal, return their names to the caller.
It would need extending though if the property types themselves are not all value types as in your example, otherwise it would only be doing a check of reference equality.
public static class PropertyCompare<T>
{
public readonly static Func<T, T, List<string>> ChangedProps;
private class PropertyComparer<T>
{
public Func<T, T, bool> Compare { get; set; }
public string PropertyName { get; set; }
}
static PropertyCompare()
{
PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
var firstObject = Expression.Parameter(typeof(T), "a");
var secondObject = Expression.Parameter(typeof(T), "b");
PropertyComparer<T>[] propertyComparers = new PropertyComparer<T>[properties.Length];
for (int i = 0; i < properties.Length; i++)
{
PropertyInfo thisProperty = properties[i];
Expression arePropertiesEqual = Expression.Equal(Expression.Property(firstObject, thisProperty), Expression.Property(secondObject, thisProperty));
Expression<Func<T, T, bool>> equalityFunc = Expression.Lambda<Func<T, T, bool>>(arePropertiesEqual, firstObject, secondObject);
PropertyComparer<T> comparer = new PropertyComparer<T>()
{
Compare = equalityFunc.Compile(),
PropertyName = properties[i].Name
};
propertyComparers[i] = comparer;
}
ChangedProps = new Func<T,T,List<string>>((a,b) =>
{
List<string> changedFields = new List<string>();
foreach (PropertyComparer<T> comparer in propertyComparers)
{
if (comparer.Compare(a, b))
continue;
changedFields.Add(comparer.PropertyName);
}
return changedFields;
});
}
}
public class Order
{
public int OrderNumber {get;set;}
public DateTime OrderDate {get;set; }
public string Something {get; set; }
}
static void Main(string[] args)
{
Order myOrder1 = new Order() { OrderDate = DateTime.Today, OrderNumber = 1, Something = "bleh" };
Order myOrder2 = new Order() { OrderDate = DateTime.Today.AddDays(1), OrderNumber = 1, Something = "bleh" };
List<string> changedFields = PropertyCompare<Order>.ChangedProps(myOrder1, myOrder2);
Console.ReadKey();
}
If you're using a log like txt you can make your function return's a string, like this bellow:
public string ReturnFields(TObject objectSaved, TObject objectChanged)
{
var sb = new StringBuilder();
if(!objectSaved.Name.Equals(objectChanged.Name)
{
sb.Append("Name was changed from " + objectSaved.Name +" to: " + objectChanged.Name)
}
if(!objectSaved.OrderDate.Equals(objectChanged.OrderDate)
{
sb.Append("The date whas changed from " + objectSaved.OrderDate+" to: " + objectChanged.OrderDate)
}
return sb.ToString();
}
It's just a simple way, you can read a little about Linq expressions to do it to.

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

Categories