Comparing against child property in generic/dynamic linq predicate with reflection - linq

I am having to generically build a comparative predicate for an Entity Framework Linq query. I'm using reflection and am able to build a single level Lambda expression without any trouble. However where I am starting to get stuck is I have an Entity that has a relationship
public class Parent {
public virtual Child child { get; set; }
.... Other Stuff...
}
public class Child {
public int property { get; set; }
public virtual Parent parent { get; set; }
.... Other Stuff.....
}
How can I can I pass in "Child.property" into Reflection to be able to create a lambda expression comparing and come up with a lambda expression similar to item => item.Child.property == value?

I think you are looking for this:
ParameterExpression parameter = Expression.Parameter(typeof(Parent), "item");
Expression child = Expression.PropertyOrField(parameter, "child");
Expression childProperty = Expression.PropertyOrField(child, "property");
int value = 1;
Expression comparison = Expression.Equal(childProperty, Expression.Constant(value));
Expression<Func<Parent, bool>> lambda = Expression.Lambda<Func<Parent, bool>>(comparison, parameter);
var sample = new[] { new Parent() { child = new Child() { property = 1 } } };
var result = sample.Where(lambda.Compile());

I assume you are wanting a generic solution supporting nested properties:
public Expression buildLambda(Type startingType, string propertyPath, object value) {
var parameter=Expression.Parameter(startingType,"item");
var valueExpression = Expression.Constant(value);
var propertyExpression=propertyPath.Split('.').Aggregate(parameter,(Expression parent,string path)=>Expression.Property(parent,path));
return Expression.Lambda(Expression.Equal(propertyExpression,valueExpression),parameter);
}

Related

Apply Expression to object property in an Expression

I have a class with a property that is another class. I also have an Expression that maps each from other classes. How do I combine these two expressions into one expression without compiling the second Expression?
public class ClassA
{
public int SomeProperty { get; set; }
}
public class MappedClassA
{
public int MappedProperty { get; set; }
}
public class ClassB
{
public ClassA ClassAProperty { get; set; }
//snip...
}
public class MappedClassB
{
public MappedClassA MappedClassAProperty {get; set; }
}
public Expression<Func<ClassA, MappedClassA>> MapAExpression()
{
return a => new MappedClassA()
{
MappedProperty = a.SomeProperty
};
}
public Expression<Func<ClassB, MappedClassB>> MapBExpression()
{
return b => new MappedClassB()
{
//this should be done with the above expression
MappedClassAProperty = new MappedClassA()
{
MappedProperty = b.ClassAProperty.SomeProperty
}
};
}
This is possible without compiling the expression, but unfortunately not by using the simple lambda syntax to construct the expression.
You will have to create the expression tree "by hand", using expression methods like Expression.New() and Expression.Lambda().
This can get unreadable very quickly.
I tried assembling such an expression from memory below, but I lack practice; this is untested and there are possibly some bugs left.
public Expression<Func<ClassB, MappedClassB>> MapBExpression()
{
// generate a parameter of type ClassB.
// This is the parameter "b" our final lambda expression will accept.
var classBparam = Expression.Parameter(typeof(ClassB));
// access b.ClassAProperty; this is the property
// that we want to pass to the expression returned by MapAExpression()
var memberAccess = Expression.MakeMemberAccess(
classBparam,
typeof(ClassB).GetProperty("ClassAProperty"));
// invoke the lambda returned by MapAExpression()
//with the parameter b.ClassAProperty
var invocation = Expression.Invoke( MapAExpression(), memberAccess );
// create a new MappedClassB(), this is the object that will be returned
// by the expression we are currently creating
var ctor = Expression.New(typeof(MappedClassB));
// We want to assign something to the MappedClassB.MappedClassAProperty
var mappedClassAProperty =
typeof(MappedClassB).GetProperty("MappedClassAProperty");
// specifically, we want to assign the result of our MapAExpression(),
// when invoked with the parameter b.ClassAProperty
var mappedClassAAssignment =
Expression.Bind(mappedClassAProperty, invocation);
// Here we initialize the MappedClassAProperty,
// after creating the new MappedClassB.
// We initialize it with the assignment we just created
var memberInit = Expression.MemberInit(ctor, mappedClassAAssignment);
// finally, we construct the lambda
//that does all of the above, given a parameter of type ClassB
return Expression.Lambda<Func<ClassB, MappedClassB>>(memberInit, classBparam);
// this expression should now be equivalent to:
// return b => new MappedClassB()
// {
// MappedClassAProperty = new MappedClassA()
// {
// MappedProperty = b.ClassAProperty.SomeProperty
// }
// };
}

Change a LINQ expression predicate from one type to another

I have two unrelated classes. One is exposed as API, and the other is used internally by 3rd party API.
Entity is exposed from our API, while EntityProvider is from the 3rd party assembly.
class Entity
{
public A { get; set; }
}
class EntityProvider
{
public A { get; set; }
}
Consumers of our API will provide predicates of the form Expression <Func<Entity, bool>> and I need to modify it to Expression <Func<EntityProvider, bool>> so that I can pass the same to internal 3rd party assembly.
Please help with this conversion.
Since Expressions in .NET are immutable, the only way to do this is to rebuild the whole expression. To do this usually involves inheriting from the ExpressionVisitor class. Depending on the complexity of the expressions you have to convert this could be quite complicated.
This is a simple example of a visitor that will work with simple expressions( like x=>x.Someproperty == somevalue ). It's just an example to get you started and it's in no way finished or tested(it won't handle method calls in the expression for example)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
//Type from which to convert
public class A
{
public int Property1 { get; set; }
public int Property2 { get; set; }
}
//Type to which we want the Expression converted
public class B
{
public int Property1 { get; set; }
public int Property2 { get; set; }
}
class Program
{
static void Main(string[] args)
{
//the expression we want to convert expresion
Expression<Func<A, bool>> expA = x => x.Property1 == 6 && x.Property2 == 3;
var visitor = new ParameterTypeVisitor<A,B>(expA);
var expB = visitor.Convert();
var b = new B() { Property1 = 6, Property2 = 3 };
//try the converted expression
var result = expB.Compile().Invoke(b);
}
}
public class ParameterTypeVisitor<TFrom,TTo> : ExpressionVisitor
{
private Dictionary<string, ParameterExpression> convertedParameters;
private Expression<Func<TFrom, bool>> expression;
public ParameterTypeVisitor(Expression<Func<TFrom,bool>> expresionToConvert )
{
//for each parameter in the original expression creates a new parameter with the same name but with changed type
convertedParameters = expresionToConvert.Parameters
.ToDictionary(
x => x.Name,
x => Expression.Parameter(typeof (TTo), x.Name)
);
expression = expresionToConvert;
}
public Expression<Func<TTo,bool>> Convert()
{
return (Expression<Func<TTo, bool>>)Visit(expression);
}
//handles Properties and Fields accessors
protected override Expression VisitMember(MemberExpression node)
{
//we want to replace only the nodes of type TFrom
//so we can handle expressions of the form x=> x.Property.SubProperty
//in the expression x=> x.Property1 == 6 && x.Property2 == 3
//this replaces ^^^^^^^^^^^ ^^^^^^^^^^^
if (node.Member.DeclaringType == typeof(TFrom))
{
//gets the memberinfo from type TTo that matches the member of type TFrom
var memeberInfo = typeof (TTo).GetMember(node.Member.Name).First();
//this will actually call the VisitParameter method in this class
var newExp = Visit(node.Expression);
return Expression.MakeMemberAccess(newExp, memeberInfo);
}
else
{
return base.VisitMember(node);
}
}
// this will be called where ever we have a reference to a parameter in the expression
// for ex. in the expression x=> x.Property1 == 6 && x.Property2 == 3
// this will be called twice ^ ^
protected override Expression VisitParameter(ParameterExpression node)
{
var newParameter = convertedParameters[node.Name];
return newParameter;
}
//this will be the first Visit method to be called
//since we're converting LamdaExpressions
protected override Expression VisitLambda<T>(Expression<T> node)
{
//visit the body of the lambda, this will Traverse the ExpressionTree
//and recursively replace parts of the expression we for which we have matching Visit methods
var newExp = Visit(node.Body);
//this will create the new expression
return Expression.Lambda(newExp,convertedParameters.Select(x=>x.Value));
}
}

Dynamic LINQ Expression for sorting navigation property

MVC3, Entity Framework 4.1 Code first.
Working with 2 tables
Model:
public class UniversityMaster
{
[Key]
public string UniversityId { get; set; }
public string UniversityName { get; set; }
}
public class ProgramMaster
{
[Key]
public string ProgramId { get; set; }
public string ProgramName { get; set; }
public string UniversityId { get; set; }
public virtual UniversityMaster University { get; set; } // navigation property
}
Dynamic expression for sorting (just to avoid a switch case statement):
public virtual IQueryable< ProgramMaster > GetQueryableSort(string sortField="", string sortDirection="")
{
IQueryable<ProgramMaster> query = _dbSet;
ParameterExpression pe = Expression.Parameter(typeof(ProgramMaster), string.Empty);
MemberExpression property = Expression.PropertyOrField(pe, sortField);
//get a exception here if the sort field is of navigation property (University.UniversityName)
LambdaExpression lambda = Expression.Lambda(property, pe);
if (sortDirection == "ASC")
orderbydir = "OrderBy";
else
orderbydir = "OrderByDescending";
MethodCallExpression call = Expression.Call(typeof(Queryable),
orderbydir, new Type[] { typeof(TEntity), property.Type }, query.Expression, Expression.Quote(lambda));
var returnquery = (IOrderedQueryable<ProgramMaster>)query.Provider.CreateQuery< ProgramMaster >(call);
return returnquery;
}
The page is displaying a grid with two columns Program Name and University Name using webgrid. The sorting work fine for Program Name column, however fails if sorted by University Name as this property is in UniversityMaster and the Expression.PropertyOrField searches this property in ProgramMaster. Here is the exception:
University.UniversityName' is not a member of type 'App.Core.Model.ProgramMaster
My question is how I make this work for navigation properties of my model class.
Hope I was able explain the scenario. Any help is appreciated.
Well that's because the MemberExpression is trying to call a member named Univerty.UniversityName on the parameter. What you want to do is call a member named Univerity on the parameter, then call UniversityName on that. Effectively, you need to iteratively resolve the property names.
public virtual IQueryable< ProgramMaster > GetQueryableSort(string sortField = "", string sortDirection = "")
{
IQueryable<ProgramMaster> query = _dbSet;
var propertyNames = sortField.Split(".");
ParameterExpression pe = Expression.Parameter(typeof(ProgramMaster), string.Empty);
Expression property = pe;
foreach(var prop in propertyName)
{
property = Expression.PropertyOrField(property, prop);
}
LambdaExpression lambda = Expression.Lambda(property, pe);
if (sortDirection == "ASC")
orderbydir = "OrderBy";
else
orderbydir = "OrderByDescending";
MethodCallExpression call = Expression.Call(
typeof(Queryable),
orderbydir,
new Type[] { typeof(TEntity), property.Type },
query.Expression,
Expression.Quote(lambda));
var returnquery = (IOrderedQueryable<ProgramMaster>)query.Provider.CreateQuery<ProgramMaster>(call);
return returnquery;
}
Microsoft has a DynamicQueryable class which can be used to dynamically construct certain portions of a LINQ query using strings. With this you can say myQuery.OrderBy("University.UniversityName") and it will handle building the expression. The same library also supports dynamic construction of SELECT and WHERE clauses.
You can find a copy of the source as part of the excellent EntityFramework.Extended package by Loresoft. Microsoft's file is at https://github.com/loresoft/EntityFramework.Extended/blob/master/Source/EntityFramework.Extended/Dynamic/DynamicQueryable.cs

Spring.Caching.AspNetCache - Condition based on ReturnValue - Condition in Spring Expression Language

I use Cache Aspect with ASP.NET Cache. I need create condition based on ReturnValue.
I simplified my problem. I use CacheResult aspect on method wich return simple POCO object.
Here is definition:
public class MyData
{
public string MyProperty { get; set; }
}
public class MyResponse
{
public int MyId { get; set; }
public MyData [] Result { get; set; }
}
I need create condition for cache - Cache result only if MyResponse.MyData.Lenght is bigger then batch limit.
[CacheResult("AspNetCache", "'MyResponse.MyId=' + #id",
Condition = "MyResponse.Result.Length > #batchLimit")]
public MyResponse GetResponse(int id, int batchLimit)
{
Thread.Sleep(5000);
return new MyResponse
{
MyId = 1,
Result =
new MyData[]
{
new MyData {MyProperty = "A"}, new MyData {MyProperty = "B"},
new MyData {MyProperty = "C"},
}
};
}
I tried this definition of condition:
Condition = "MyResponse.Result.Length > #batchLimit"
I got this error:
'MyResponse' node cannot be resolved for the specified context
[Sample.MyResponse].
So I tried second version:
Condition = "'MyResponse.Result.Length' > #batchLimit"
Finished with error:
Cannot compare instances of [System.String] and [System.Int32] because they cannot be coerced to the same type.
I google it I can use keyword ReturnValue something like this:
Condition = "#ReturnValue != null"
But I don't know how I can access to MyResponse.MyData.Length.
the context for the evaluation of the condition expression is the return value, so just do this:
Condition = "Result.Length > #batchLimit"
equivalent to
Condition = "#root.Result.Length > #batchLimit"

Why predicate isn't filtering when building it via reflection

I'm building a rather large filter based on an SearchObject that has 50+ fields that can be searched.
Rather than building my where clause for each one of these individually I thought I'd use some slight of hand and try building custom attribute suppling the necessary information and then using reflection to build out each of my predicate statements (Using LinqKit btw). Trouble is, that the code finds the appropriate values in the reflection code and successfully builds a predicate for the property, but the "where" doesn't seem to actually generate and my query always returns 0 records.
The attribute is simple:
[AttributeUsage(AttributeTargets.Property, AllowMultiple=true)]
public class FilterAttribute: Attribute
{
public FilterType FilterType { get; set; } //enum{ Object, Database}
public string FilterPath { get; set; }
//var predicate = PredicateBuilder.False<Metadata>();
}
And this is my method that builds out the query:
public List<ETracker.Objects.Item> Search(Search SearchObject, int Page, int PageSize)
{
var predicate = PredicateBuilder.False<ETracker.Objects.Item>();
Type t = typeof(Search);
IEnumerable<PropertyInfo> pi = t.GetProperties();
string title = string.Empty;
foreach (var property in pi)
{
if (Attribute.IsDefined(property, typeof(FilterAttribute)))
{
var attrs = property.GetCustomAttributes(typeof(FilterAttribute),true);
var value = property.GetValue(SearchObject, null);
if (property.Name == "Title")
title = (string)value;
predicate.Or(a => GetPropertyVal(a, ((FilterAttribute)attrs[0]).FilterPath) == value);
}
}
var res = dataContext.GetAllItems().Take(1000)
.Where(a => SearchObject.Subcategories.Select(b => b.ID).ToArray().Contains(a.SubCategory.ID))
.Where(predicate);
return res.ToList();
}
The SearchObject is quite simple:
public class Search
{
public List<Item> Items { get; set; }
[Filter(FilterType = FilterType.Object, FilterPath = "Title")]
public string Title { get; set; }
...
}
Any suggestions will be greatly appreciated. I may well be going way the wrong direction and will take no offense if someone has a better alternative (or at least one that works)
You're not assigning your predicate anywhere. Change the line to this:
predicate = predicate.Or(a => GetPropertyVal(a, ((FilterAttribute)attrs[0]).FilterPath) == value);

Resources