Dynamic linq Building Expression - c#

I need to create a dynamic linq expression for a dynamic search.The basic search is working but it fails to work with collection.
I am able to get the book's title and author but fails to get the required page heading.
I get the exception in line "left11 = Expression.Property(page1, "Heading");".
I think the expression that i built is unable to recognise the List. How could this be possible?
Please see the below code and stacktrace exception.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
namespace XMLStorageAndFilter
{
public class Books
{
public Books()
{
Page = new List<Page>();
}
public string Title { get; set; }
public Author Author { get; set; }
public List<Page> Page { get; set; }
}
public class Author
{
public string FirstName { get; set; }
}
public class Page
{
public string Heading { get; set; }
}
public class Program2
{
static void Main()
{
Page page = new Page();
page.Heading = "Heading";
Books bok = new Books();
bok.Title = "Title";
bok.Author = new Author() { FirstName = "FirstName" };
bok.Page.Add(page);
List<Books> testList = new List<Books>();
testList.Add(bok);
IQueryable<Books> queryableTestData = testList.AsQueryable<Books>();
ParameterExpression pe11 = Expression.Parameter(typeof(Books), "p");
Expression left11 = Expression.Property(pe11, "Title");
Expression right11 = Expression.Constant("Title");
Expression e11 = Expression.Equal(left11, right11);
var author = Expression.Property(pe11, "Author");
left11 = Expression.Property(author, "FirstName");
right11 = Expression.Constant("FirstName");
Expression e21 = Expression.Equal(left11, right11);
Expression predicateBody11 = Expression.And(e11, e21);
Expression<Func<Books, bool>> condition = Expression.Lambda
<Func<Books, bool>>(predicateBody11, new ParameterExpression[] { pe11 });
var q = queryableTestData.Where(condition);
var page1 = Expression.Property(pe11, "Page");
left11 = Expression.Property(page1, "Heading");
right11 = Expression.Constant("Heading");
Expression e22 = Expression.Equal(left11, right11);
Expression predicateBody12 = Expression.And(e11, e22);
Expression<Func<Books, bool>> condition2 = Expression.Lambda
<Func<Books, bool>>(predicateBody12, new ParameterExpression[] { pe11 });
var qq1 = queryableTestData.Where(condition2);
}
}
}
Exception Message:-
{"Instance property 'Heading' is not defined for type >'System.Collections.Generic.List`1[XMLStorageAndFilter.Page]'"}
StackTrace:-
at System.Linq.Expressions.Expression.Property(Expression expression, String propertyName)
at XMLStorageAndFilter.Program2.Main() in c:\Users\Administrator\Documents\Visual Studio 2013\Projects\XMLStorageAndFilter\NavProperty.cs:line 61
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()

You can use the method described here.
You would need to cast the result of the method to Expression<Func<T,bool>>. T being your type.
I will provide an complete example when i get home.
Edit:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Collections;
using System.Reflection;
namespace ExpressionPredicateBuilder
{
public enum OperatorComparer
{
Contains,
StartsWith,
EndsWith,
Equals = ExpressionType.Equal,
GreaterThan = ExpressionType.GreaterThan,
GreaterThanOrEqual = ExpressionType.GreaterThanOrEqual,
LessThan = ExpressionType.LessThan,
LessThanOrEqual = ExpressionType.LessThan,
NotEqual = ExpressionType.NotEqual
}
public class ExpressionBuilder
{
public static Expression<Func<T,bool>> BuildPredicate<T>(object value, OperatorComparer comparer, params string[] properties)
{
var parameterExpression = Expression.Parameter(typeof(T), typeof(T).Name);
return (Expression<Func<T, bool>>)BuildNavigationExpression(parameterExpression, comparer, value, properties);
}
private static Expression BuildNavigationExpression(Expression parameter, OperatorComparer comparer, object value, params string[] properties)
{
Expression resultExpression = null;
Expression childParameter, predicate;
Type childType = null;
if (properties.Count() > 1)
{
//build path
parameter = Expression.Property(parameter, properties[0]);
var isCollection = typeof(IEnumerable).IsAssignableFrom(parameter.Type);
//if it´s a collection we later need to use the predicate in the methodexpressioncall
if (isCollection)
{
childType = parameter.Type.GetGenericArguments()[0];
childParameter = Expression.Parameter(childType, childType.Name);
}
else
{
childParameter = parameter;
}
//skip current property and get navigation property expression recursivly
var innerProperties = properties.Skip(1).ToArray();
predicate = BuildNavigationExpression(childParameter, comparer, value, innerProperties);
if (isCollection)
{
//build subquery
resultExpression = BuildSubQuery(parameter, childType, predicate);
}
else
{
resultExpression = predicate;
}
}
else
{
//build final predicate
resultExpression = BuildCondition(parameter, properties[0], comparer, value);
}
return resultExpression;
}
private static Expression BuildSubQuery(Expression parameter, Type childType, Expression predicate)
{
var anyMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Any" && m.GetParameters().Length == 2);
anyMethod = anyMethod.MakeGenericMethod(childType);
predicate = Expression.Call(anyMethod, parameter, predicate);
return MakeLambda(parameter, predicate);
}
private static Expression BuildCondition(Expression parameter, string property, OperatorComparer comparer, object value)
{
var childProperty = parameter.Type.GetProperty(property);
var left = Expression.Property(parameter, childProperty);
var right = Expression.Constant(value);
var predicate = BuildComparsion(left, comparer, right);
return MakeLambda(parameter, predicate);
}
private static Expression BuildComparsion(Expression left, OperatorComparer comparer, Expression right)
{
var mask = new List<OperatorComparer>{
OperatorComparer.Contains,
OperatorComparer.StartsWith,
OperatorComparer.EndsWith
};
if(mask.Contains(comparer) && left.Type != typeof(string))
{
comparer = OperatorComparer.Equals;
}
if(!mask.Contains(comparer))
{
return Expression.MakeBinary((ExpressionType)comparer, left, Expression.Convert(right,left.Type));
}
return BuildStringCondition(left, comparer, right);
}
private static Expression BuildStringCondition(Expression left, OperatorComparer comparer, Expression right)
{
var compareMethod = typeof(string).GetMethods().Single(m => m.Name.Equals(Enum.GetName(typeof(OperatorComparer), comparer)) && m.GetParameters().Count() == 1);
//we assume ignoreCase, so call ToLower on paramter and memberexpression
var toLowerMethod = typeof(string).GetMethods().Single(m => m.Name.Equals("ToLower") && m.GetParameters().Count() == 0);
left = Expression.Call(left, toLowerMethod);
right = Expression.Call(right, toLowerMethod);
return Expression.Call(left, compareMethod, right);
}
private static Expression MakeLambda(Expression parameter, Expression predicate)
{
var resultParameterVisitor = new ParameterVisitor();
resultParameterVisitor.Visit(parameter);
var resultParameter = resultParameterVisitor.Parameter;
return Expression.Lambda(predicate, (ParameterExpression)resultParameter);
}
private class ParameterVisitor : ExpressionVisitor
{
public Expression Parameter
{
get;
private set;
}
protected override Expression VisitParameter(ParameterExpression node)
{
Parameter = node;
return node;
}
}
}
}
This can be used like
var predicate = ExpressionBuilder.BuildPredicate<Books>("Heading",OperatorComparer.Equals,"Page","Heading");
query = query.Where(predicate);

Based on your description I'm not sure that you need Expression. Creating an Expression with a complex object model is quite difficult. Do you really need to create dynamic expression or you simply need to create a dynamic query? If the object model is fixed then you don't need Expression.
I suggest first of all to clean your object model:
Rename the Books class to Book (this class represents a Book not a list of books)
Rename property Page to Pages (this property returns a list of pages)
Now you can write a dynamic where using just LINQ and one or more helper functions one for each property that you need to search. For example to search for Heading you can write:
private static bool SearchByHeading(Book b, string heading)
{
if (string.IsNullOrEmpty(heading))
return true;
else
return b.Pages.Any(p => p.Heading == heading);
}
Here you can also see why your previous code didn't work. The expression to search for a given Heading is book.Pages.Any(p => p.Heading == x) and not book.Pages.Heading == x.
In any case given one or more functions like this you can rewrite your code with something like:
using System.Collections.Generic;
using System.Linq;
namespace XMLStorageAndFilter
{
public class Book
{
public Book()
{
Pages = new List<Page>();
}
public string Title { get; set; }
public Author Author { get; set; }
public List<Page> Pages { get; set; }
}
public class Author
{
public string FirstName { get; set; }
}
public class Page
{
public string Heading { get; set; }
}
public class Program2
{
static void Main()
{
Page page = new Page();
page.Heading = "Heading1";
Book bok = new Book();
bok.Title = "Title1";
bok.Author = new Author() { FirstName = "FirstName1" };
bok.Pages.Add(page);
List<Book> testList = new List<Book>();
testList.Add(bok);
var searchResult = Search(testList,
title: "Title1",
author: "FirstName1",
heading: "Heading1");
}
private static IEnumerable<Book> Search(IEnumerable<Book> books, string author = null, string title = null, string heading = null)
{
return books
.Where((b) => SearchByAuthor(b, author))
.Where((b) => SearchByHeading(b, heading))
.Where((b) => SearchByTitle(b, title))
.ToList();
}
private static bool SearchByAuthor(Book b, string author)
{
if (string.IsNullOrEmpty(author))
return true;
else
return b.Author.FirstName == author;
}
private static bool SearchByTitle(Book b, string title)
{
if (string.IsNullOrEmpty(title))
return true;
else
return b.Title == title;
}
private static bool SearchByHeading(Book b, string heading)
{
if (string.IsNullOrEmpty(heading))
return true;
else
return b.Pages.Any(p => p.Heading == heading);
}
}
}
I have skipped search values when null or empty, just an example.
This code has also the advantage that is verified at compile time.

Update
The way to query a collection has been answered in the following:
Building a dynamic expression tree to filter on a collection property
Original Response
I believe Davide Lcardi is correct with his statement:
Heading is book.Pages.Any(p => p.Heading == x) and not book.Pages.Heading == x.
if you want to query the list you need to use Any() method to do so. I could not get it exactly right but it should look something like the following using Expression.Call:
ParameterExpression pe41 = Expression.Parameter(typeof (Page), "pg");
Expression left41 = Expression.Property(pe41, "Heading");
Expression right41 = Expression.Constant("Heading");
Expression e41 = Expression.Equal(left41, right41);
var methodCall = Expression.Call( Expression.Property(pe11, "Pages"), "Any", new Type[] {typeof(Page), typeof(Boolean)}, e41 );
I am getting this error: No method 'Any' exists on type 'System.Collections.Generic.List`1[SO.Page]'. SO is my NameSpace where class Page exists.
I think I am not sending the correct Type or the whole call may be incorrect. I think this is the correct direction thought to help you find your solution.
Here are some examples I was looking at:
http://msdn.microsoft.com/en-us/library/bb349020(v=vs.110).aspx
http://msdn.microsoft.com/en-us/library/dd402755(v=vs.110).aspx
http://community.bartdesmet.net/blogs/bart/archive/2009/08/10/expression-trees-take-two-introducing-system-linq-expressions-v4-0.aspx
http://blogs.msdn.com/b/csharpfaq/archive/2009/09/14/generating-dynamic-methods-with-expression-trees-in-visual-studio-2010.aspx

On the whole, it is not bad to consider DynamicLinq when you are dealing with dynamic matters

You should use Contains - you are looking inside a list - not Equals.

Related

How to get enumerable or collection of values from WhereSelectListIterator

I need to write a generic method for getting distinct values and propertyName is not known in advance. I want to do it using the LINQ expression. I am trying the below way but when getting the result from SelectMethod invoke I am getting WhereSelectListIterator. I want to convert it to IEnumerable so I can call the Distinct method. But I am not to cast it to IEnumerable(as it's not implemented it). How to get Enumerable back from
WhereSelectListIterator or is there any way I can get IEnumerable directly from invoke of generic method.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp10
{
public class EmployeeEqualityComparer : IEqualityComparer<Employee>
{
public bool Equals(Employee x, Employee y)
{
return x.Id == y.Id;
}
public int GetHashCode(Employee obj)
{
return obj.Id;
}
}
class Program
{
static void Main(string[] args)
{
var employees = new List<Employee>()
{ new Employee(){Id=1},
new Employee(){Id=2},
new Employee(){Id=1}};
var values1 = employees.Select(obj => obj.Id).Distinct();
var values2 = employees.Distinct(new EmployeeEqualityComparer());
var values= GetDistinctValue(employees, "Id");
}
private static readonly MethodInfo DistinctMethod = typeof(Enumerable).GetMethods().First(method =>
method.Name == "Distinct" &&
method.GetParameters().Length == 1);
private static readonly MethodInfo SelectMethod = typeof(Enumerable).GetMethods().First(method =>
method.Name == "Select" &&
method.GetParameters().Length == 2);
public static IEnumerable<object> GetDistinctValue<T>(IEnumerable<T> records, string propertyName)
{
try
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(T));
Expression propertyExpression = Expression.Property(parameterExpression, propertyName);
var lambda = Expression.Lambda(propertyExpression, parameterExpression);
var propertyType = propertyExpression.Type;
// MethodCallExpression compareCall = Expression.Call(typeof(Program), "Compare", Type.EmptyTypes, propertyExpression, Expression.Constant(""), Expression.Constant(""), Expression.Constant(""));
//LambdaExpression lambda = Expression.Lambda<Func<T, bool>>(compareCall, parameterExpression);
MethodInfo genericMethod = SelectMethod.MakeGenericMethod(typeof(T),propertyType);
var result = genericMethod.Invoke(null, new object[] { records, lambda.Compile() });
MethodInfo distinctGenericMethod = DistinctMethod.MakeGenericMethod(result.GetType());
var finalResult = distinctGenericMethod.Invoke(null, new object[] { result});
return null;
}
catch(Exception exception)
{
Console.WriteLine(exception.Message);
}
return null;
}
}
public class Employee
{
public int Age { get; set; }
public string Name { get; set; }
public int Id { get; set; }
}
}
This is working version of GetDistinctValue. Main idea that you can work with IEnumerable via IQueryable which is dynamic by default.
public static IEnumerable<object> GetDistinctValue<T>(IEnumerable<T> records, string propertyName)
{
var parameterExpression = Expression.Parameter(typeof(T), "e");
var body = (Expression)Expression.Property(parameterExpression, propertyName);
if (body.Type != typeof(object))
{
body = Expression.Convert(body, typeof(object));
}
var lambda = Expression.Lambda(body, parameterExpression);
// turn IEnumerable into IQueryable
var queryable = records.AsQueryable();
var queryExpression = queryable.Expression;
// records.Select(e => (object)e.propertyName)
queryExpression = Expression.Call(typeof(Queryable), nameof(Queryable.Select),
new[] { typeof(T), typeof(object) }, queryExpression, lambda);
// records.Select(e => (object)e.propertyName).Distinct()
queryExpression = Expression.Call(typeof(Queryable), nameof(Queryable.Distinct), new[] { typeof(object) },
queryExpression);
// creating IQueryable<object> from generated expression
var resultQuery = queryable.Provider.CreateQuery<object>(queryExpression);
// turn IQueryable into IEnumerable
return resultQuery.AsEnumerable();
}

Generic Expression tree with 'OR' clause for each supplied property

I have created a generic search extension method for IQueryable that enables you to search for a single property to see if a search term is contained within it.
http://jnye.co/Posts/6/c%23-generic-search-extension-method-for-iqueryable
I now want to enable the user to select multiple properties to search within each, matching if any property contains the text.
The code:
The user enters the following code to perform this search:
string searchTerm = "Essex";
context.Clubs.Search(searchTerm, club => club.Name, club => club.County)
//Note: If possible I would rather something closer to the following syntax...
context.Clubs.Search(club => new[]{ club.Name, club.County}, searchTerm);
// ... or, even better, something similar to this...
context.Clubs.Search(club => new { club.Name, club.County}, searchTerm);
This will return any golf club with 'Essex' in the Name or as the County.
public static IQueryable<TSource> Search<TSource>(this IQueryable<TSource> source, string searchTerm, params Expression<Func<TSource, string>>[] stringProperties)
{
if (String.IsNullOrEmpty(searchTerm))
{
return source;
}
// The lamda I would like to reproduce:
// source.Where(x => x.[property1].Contains(searchTerm)
// || x.[property2].Contains(searchTerm)
// || x.[property3].Contains(searchTerm)...)
//Create expression to represent x.[property1].Contains(searchTerm)
var searchTermExpression = Expression.Constant(searchTerm);
//Build parameters
var parameters = stringProperties.SelectMany(prop => prop.Parameters);
Expression orExpression = null;
//Build a contains expression for each property
foreach (var stringProperty in stringProperties)
{
var checkContainsExpression = Expression.Call(stringProperty.Body, typeof(string).GetMethod("Contains"), searchTermExpression);
if (orExpression == null)
{
orExpression = checkContainsExpression;
}
//Build or expression for each property
orExpression = Expression.OrElse(orExpression, checkContainsExpression);
}
var methodCallExpression = Expression.Call(typeof(Queryable),
"Where",
new Type[] { source.ElementType },
source.Expression,
Expression.Lambda<Func<TSource, bool>>(orExpression, parameters));
return source.Provider.CreateQuery<TSource>(methodCallExpression);
}
The error
If I change the number of parameters supplied to 1:
Expression.Lambda<Func<TSource, bool>>(orExpression, parameters.First()));
I get a new error:
UPDATE
I have written a post on the work discussed in this question. Check it out on GitHub too.
Here we go; you were pretty close - as I noted in comments, the key piece here is to use ExpressionVisitor to re-write the trees in terms of the single parameter you want to keep:
using System;
using System.Linq;
using System.Linq.Expressions;
static class Program
{
static void Main()
{
var data = new[] { new Foo { A = "x1", B = "y1", C = "y1" }, new Foo { A = "y2", B = "y2", C = "y2" },
new Foo { A = "y3", B = "y3", C = "x3" } }.AsQueryable();
var result = data.Search("x", x => x.A, x => x.B, x => x.C);
foreach (var row in result)
{
Console.WriteLine("{0}, {1}, {2}", row.A, row.B, row.C);
}
}
class Foo
{
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
}
public class SwapVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public SwapVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
public static Expression Swap(Expression body, Expression from, Expression to)
{
return new SwapVisitor(from, to).Visit(body);
}
}
public static IQueryable<TSource> Search<TSource>(this IQueryable<TSource> source, string searchTerm, params Expression<Func<TSource, string>>[] stringProperties)
{
if (String.IsNullOrEmpty(searchTerm))
{
return source;
}
if (stringProperties.Length == 0) return source.Where(x => false);
// The lamda I would like to reproduce:
// source.Where(x => x.[property1].Contains(searchTerm)
// || x.[property2].Contains(searchTerm)
// || x.[property3].Contains(searchTerm)...)
//Create expression to represent x.[property1].Contains(searchTerm)
var searchTermExpression = Expression.Constant(searchTerm);
var param = stringProperties[0].Parameters.Single();
Expression orExpression = null;
//Build a contains expression for each property
foreach (var stringProperty in stringProperties)
{
// re-write the property using the param we want to keep
var body = SwapVisitor.Swap(stringProperty.Body, stringProperty.Parameters.Single(), param);
var checkContainsExpression = Expression.Call(
body, typeof(string).GetMethod("Contains"), searchTermExpression);
if (orExpression == null)
{
orExpression = checkContainsExpression;
}
else
{ // compose
orExpression = Expression.OrElse(orExpression, checkContainsExpression);
}
}
var lambda = Expression.Lambda<Func<TSource, bool>>(orExpression, param);
return source.Where(lambda);
}
}

Using given properties as strings

I would like to use a single, general method to retrieve an ordered list for a given string representing a property inside a lambda expression.
I know people requested this before but it didn't work for me. I tried this and it threw error:
db.Books.OrderByDescending(x => x.GetType().GetProperty("Discount").GetValue(x,null))
.Take(3);
I'm using this at the moment:
public IQueryable<Book> GetCheapestBooks()
{
return db.Books.OrderBy(x => x.Discount)
.Take(3);
}
maybe this is what you are looking for:
Dynamic Linq
With this you can write queries like:
var result = db.Books.OrderBy( "Discount" ).Take( 3 );
Simple console application.
class A
{
public int prop1 { get; set; }
public int prop2 { get; set; }
}
class Program
{
static IEnumerable<T> GenericOrderByDescending<T>(IEnumerable<T> arg, string property, int take)
{
return arg.OrderByDescending(x => x.GetType().GetProperty(property).GetValue(x, null)).Take(take);
}
static void Main(string[] args)
{
IEnumerable<A> arr = new List<A>()
{
new A(){ prop1 = 1, prop2 = 2},
new A(){prop1 = 2,prop2 =2},
new A(){prop1 = 3,prop2 =2},
new A(){prop1 = 441,prop2 =2},
new A(){prop1 = 2,prop2 =2}
};
foreach(var a1 in GenericOrderByDescending<A>(arr, "prop1", 3))
{
Console.WriteLine(a1.prop1);
}
}
}
U can pass your db.Boks.AsEnumerable() as parameter for GenericOrderByDescending<T>() method. Instead of T you should type the type of your db.Boks items. My example sorts an array of instances of class A and I've got no errors, it works fine. Did I understand you correctly?
You can try with this code
public IQueryable<Book> GetCheapestBooks()
{
db.Books.OrderBy(x => x.Discount).Take(3).AsQueryable<Book>();
}
You could create an extension method which creates the property expression:
private static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName)
{
PropertyInfo prop = typeof(T).GetProperty(propertyName);
ParameterExpression paramExpr = Expression.Parameter(typeof(T), "obj");
MemberExpression propExpr = Expression.Property(paramExpr, prop);
Type funcType = typeof(Func<,>).MakeGenericType(typeof(T), prop.PropertyType);
Type keySelectorType = typeof(Expression<>).MakeGenericType(funcType);
LambdaExpression keySelector = Expression.Lambda(funcType, propExpr, paramExpr);
MethodInfo orderByMethod = typeof(Queryable).GetMethods().Single(m => m.Name == "OrderBy" && m.GetParameters().Length == 2).MakeGenericMethod(typeof(T), prop.PropertyType);
return (IOrderedQueryable<T>) orderByMethod.Invoke(null, new object[] { source, keySelector });
}

Can the DisplayName attribute be accessed without using MVC?

I have refactored out a generic CSV builder for List using lambda expressions to access the correct values
public static string ToCsv<TModel>(this List<TModel> list, string delimiter, string lineBreak, string valueWrap, params Expression<Func<TModel, object>>[] expressions)
{
var sb = new StringBuilder();
var headers = expressions.Select(m => String.Format("{0}{1}{0}", valueWrap, GetPropertyName(m))).ToArray();
sb.Append(String.Format("{0}{1}", String.Join(delimiter, headers), lineBreak));
foreach (var listItem in list)
{
var values = expressions.Select(m => String.Format("{0}{1}{0}", valueWrap, m.Compile()(listItem))).ToArray();
sb.Append(String.Format("{0}{1}", String.Join(delimiter, values), lineBreak));
}
return sb.ToString();
}
This works well, however because I am trying to move this into some common code for several projects across several servers to access. I cannot reference the System.Web.Mvc assembly
Is there a good way to access the DisplayName Attribute and if that doesnot exist, access the variable name?
My current attempt access the ((MemberExpression) expression.Body).Member.Name however it will not work if it has to convert a value to string. (ie passing an int and implicitly converting)
A second attempt of iterating over the attributes did not work for inner class (ie model => model.innerClass.property)
With a couple simple helper functions, you can eliminate all reference to the System.Web.Mvc assembly. You'll also notice in the example below that (model => model.member.property) works.
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Linq.Expressions;
using System.ComponentModel;
using System.Reflection;
namespace Test
{
public class Program
{
public static void Main(string[] args)
{
List<Class1> foobars = new List<Class1>();
foobars.Add(new Class1 { Foo = "Hello world!", Bar = -1, Skip = false, Ref = new Class2 { ToBe = true } });
string result = foobars.ToCsv(",", Environment.NewLine, "\"", m => m.Foo, m => m.Bar, m => m.Ref.ToBe);
}
public class Class1
{
[DisplayName("Foo Property")]
public string Foo { get; set; }
public int Bar { get; set; }
[DisplayName("Skipped Property")]
public bool Skip { get; set; }
[DisplayName("Reference")]
public Class2 Ref { get; set; }
}
public class Class2
{
[DisplayName("To Be or Not To Be")]
public bool ToBe { get; set; }
}
}
public static class Extensions
{
public static string ToCsv<TModel>(this List<TModel> list, string delimiter, string lineBreak, string valueWrap, params Expression<Func<TModel, object>>[] expressions)
{
var sb = new StringBuilder();
var headers = expressions.Select(m => String.Format("{0}{1}{0}", valueWrap, GetDisplayName(m))).ToArray();
sb.Append(String.Format("{0}{1}", String.Join(delimiter, headers), lineBreak));
foreach (var listItem in list)
{
var values = expressions.Select(m => String.Format("{0}{1}{0}", valueWrap, m.Compile()(listItem))).ToArray();
sb.Append(String.Format("{0}{1}", String.Join(delimiter, values), lineBreak));
}
return sb.ToString();
}
// Get DisplayName, otherwise fallback to Name
private static string GetDisplayName(LambdaExpression memberReference)
{
MemberInfo info = GetMemberInfo(memberReference);
DisplayNameAttribute displayNameAttr = Attribute.GetCustomAttribute(info, typeof(DisplayNameAttribute)) as DisplayNameAttribute;
return (displayNameAttr != null ? displayNameAttr.DisplayName : info.Name);
}
// Can be swapped for your favourite GetMemberInfo/GetPropertyInfo utility method (there are many out there)
// Source: http://blog.baltrinic.com/software-development/dotnet/extension-methods-for-converting-lambda-expression-to-strings
private static MemberInfo GetMemberInfo(LambdaExpression memberReference)
{
MemberExpression memberExpression;
var unary = memberReference.Body as UnaryExpression;
if (unary != null)
//In this case the return type of the property was not object,
//so .Net wrapped the expression inside of a unary Convert()
//expression that casts it to type object. In this case, the
//Operand of the Convert expression has the original expression.
memberExpression = unary.Operand as MemberExpression;
else
//when the property is of type object the body itself is the
//correct expression
memberExpression = memberReference.Body as MemberExpression;
if (memberExpression == null || !(memberExpression.Member is MemberInfo))
throw new ArgumentException("Expression was not of the form 'x => x.member'.");
return memberExpression.Member;
}
}
}

Dynamic recursive lambda expressions

I want to create dynamic lambda expressions so that I can filter a list using a set of filtering parameters. This is what I have so far:
The expression is built using the methods bellow, where T is the object type of the list
public static Expression<Func<T, bool>> GetExpression<T>(IList<DynamicFilter> filters)
{
if (filters.Count == 0)
return null;
ParameterExpression param = Expression.Parameter(typeof(T), "t");
Expression exp = null;
if (filters.Count == 1)
exp = GetExpression<T>(param, filters[0]);
[...]
return Expression.Lambda<Func<T, bool>>(exp, param);
}
private static Expression GetExpression<T>(ParameterExpression param, DynamicFilter filter)
{
MemberExpression member = Expression.Property(param, filter.PropertyName);
ConstantExpression constant = Expression.Constant(filter.Value);
[...]
return Expression.Call(member, filterMethod, constant);
}
I then call
List<Example> list = ...;
var deleg = ExpressionBuilder.GetExpression<Example>(dynFiltersList).Compile();
list = list.Where(deleg).ToList();
This works just as expected with an object that contains only simple types, but if there are complex types inside, the code doesn't work anymore. For example, let's say I have a member of custom type Field inside the Example class and Field has a string property Value. If filter.PropertyName would be 'Field0' (of type Field), the code would work just fine, but if I have 'Field0.Value' I would get an obvious error stating that there is no property named 'Field0.Value' inside class Example.
I tried modifying the expression building method, like this:
MemberExpression member = null;
if (filter.PropertyName.Contains('.'))
{
string[] props = filter.PropertyName.Split('.');
ParameterExpression param1 = Expression.Parameter(typeof(T).GetProperty(props[0]).PropertyType, "t1");
member = Expression.Property(param1, props[0]);
}
else
{
member = Expression.Property(param, filter.PropertyName);
}
but then I got a Lambda parameter not in scope error when compiling the expression. I sort of understand why I get this error, but I don't know how to make this work.
Bottom line is I need to make the expression building method work recursively when forming the MemberExpression. I ultimately need to obtain a list = list.Where(deleg).ToList(); that translates to something like this list = list.Where(obj => obj.Field0.Value == 'something').ToList();
I've just started working with expressions so I don't really know too much in this area, but any help would be appreciated.
Thanks
I realize this is a fairly old post, but I had the exact same problem and found something close in an answer that Mark Gravell posted here. I just modified it slightly to meet my needs and below is the result:
private Expression GetDeepProperty(Expression parameter, string property)
{
var props = property.Split('.');
var type = parameter.Type;
var expr = parameter;
foreach (var prop in props)
{
var pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
return expr;
}
Implementation:
var method = typeof (string).GetMethod("Contains", new Type[] {typeof (string)}, null);
var lambdaParameter = Expression.Parameter(typeof(TEntity), "te");
var filterExpression = Expression.Lambda<Func<TEntity, bool>> (
filters.Select(filter => Expression.Call(GetDeepProperty(lambdaParameter, filter.Property),
method,
Expression.Constant(filter.Value))).
Where(exp => exp != null).
Cast<Expression>().
ToList().
Aggregate(Expression.Or), lambdaParameter);
I'm trying to address
so that I can filter a list using a set of filtering parameters
not by using ExpressionBuilder but instead by using a generic Filter class.
public class Filter<T> where T: class
{
private readonly Predicate<T> criteria;
public Filter(Predicate<T> criteria)
{
this.criteria = criteria;
}
public bool IsSatisfied(T obj)
{
return criteria(obj);
}
}
First we need to have some classes.
public class Player
{
public string Name { get; set; }
public int Level { get; set; }
public enum Sex { Male, Female, Other };
public Weapon Weapon { get; set; }
}
public class Weapon
{
public string Name { get; set; }
public int MaxDamage { get; set; }
public int Range { get; set; }
public WeaponClass Class { get; set; }
public enum WeaponClass { Sword, Club, Bow }
}
Then we need a list of objects.
var graywand = new Weapon { Name = "Graywand", MaxDamage = 42, Range = 1, Class = Weapon.WeaponClass.Sword };
var scalpel = new Weapon { Name = "Scalpel", MaxDamage = 33, Range = 1, Class = Weapon.WeaponClass.Sword };
var players = new List<Player> {
new Player { Name = "Fafhrd", Level = 19, Weapon = graywand },
new Player { Name = "Gray Mouser", Level = 19, Weapon = scalpel },
new Player { Name = "Freddy", Level = 9, Weapon = graywand },
new Player { Name = "Mouse", Level = 8, Weapon = scalpel}
};
Then let's create a couple of filters and add those to a list.
var powerfulSwords = new Filter<Player>(p => p.Weapon.MaxDamage>35);
var highLevels = new Filter<Player>(p => p.Level>15);
var filters = new List<Filter<Player>>();
filters.Add(powerfulSwords);
filters.Add(highLevels);
Finally filter the list by those filters
var highLevelAndPowerfulSwords = players.Where(p => filters.All(filter => filter.IsSatisfied(p)));
var highLevelOrPowerfulSwords = players.Where(p => filters.Any(filter => filter.IsSatisfied(p)));
Only "Fafhrd" will be in highLevelAndPowerfulSwords and highLevelOrPowerfulSwords will contain all players but "Mouse".
Have a look at ExpressionVisitor as described here: Replacing the parameter name in the Body of an Expression
You could generate an expression for each element in filter and combine them into one single expression using the methods below:
public static Expression<Func<T, K>> CombineAnd<T, K>(Expression<Func<T, K>> a, Expression<Func<T, K>> b)
{
ParameterExpression firstParameter = a.Parameters.First();
Expression<Func<T, K>> b1 = Expression.Lambda<Func<T, K>>(Expression.Invoke(b, firstParameter), firstParameter);
return Expression.Lambda<Func<T, K>>(Expression.And(a.Body, b1.Body), firstParameter);
}
public static Expression<Func<T, K>> CombineOr<T, K>(Expression<Func<T, K>> a, Expression<Func<T, K>> b)
{
ParameterExpression firstParameter = a.Parameters.First();
Expression<Func<T, K>> b1 = Expression.Lambda<Func<T, K>>(Expression.Invoke(b, firstParameter), firstParameter);
return Expression.Lambda<Func<T, K>>(Expression.Or(a.Body, b1.Body), firstParameter);
}

Categories