I have a form in which the user will choose the following from dropdown lists:
table_name
columnName_to_sort_by
columnName_to_search_in
The user shall enter Search_text in a text box
The form shall draw data from many tables. I want to avoid writing the sort and search for every field for each of the tables. This is why I want to use expression trees. I want to build the query dynamically.
I want to write a generic method that will generate the expression tree for the select, where and orderby methods, depending on user input. I can use System.Reflection to get the Type that is being queried (all my tables are types - I am using LinqToSql).
I do not know how to form the expression trees.
Here's what I have so far:
private static List<T> GetSortedData<T>( string sortColumnName)
{
var type = typeof(T);
var property = type.GetProperty(sortColumnName);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExp = Expression.Lambda(propertyAccess, parameter);
MethodCallExpression resultExp = Expression.Call(typeof(Queryable), "OrderBy", new Type[] { type, property.PropertyType }, WHAT_SHOULD_BE_HERE, Expression.Quote(orderByExp));
return (List<T>)Expression.Lambda(resultExp).Compile().DynamicInvoke();
}
How can I implement select, sort and orderby dynamically using expression trees?
What you have is close. Where you ask, "WHAT_SHOULD_BE_HERE", you are curious what expression to use to indicate the "source" parameter for OrderBy, which is usually implied from the operand when used as an extension method. What you need to do is change your sample to operate on IQueryable, and you need to accept that as an input parameter. Also, replace your WHAT_SHOULD_BE_HERE placeholder with "list.Expression" as shown below.
private static IEnumerable<T> GetSortedData<T>(IQueryable<T> list, string sortColumnName)
{
var type = typeof(T);
var property = type.GetProperty(sortColumnName);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.Property(parameter, property);
var orderByExp = Expression.Lambda(propertyAccess, parameter);
MethodCallExpression resultExp = Expression.Call(typeof(Queryable), "OrderBy", new[] { type, property.PropertyType }, list.Expression, Expression.Quote(orderByExp));
return (IEnumerable<T>)Expression.Lambda(resultExp).Compile().DynamicInvoke();
}
I tested this with the following code:
static void Main(string[] args)
{
var list = new List<Person>(new[]
{
new Person { FirstName = "John" },
new Person { FirstName = "Jane" }
}).AsQueryable();
foreach (var o in GetSortedData(list, "FirstName"))
Console.WriteLine(o.FirstName);
}
public class Person
{
public string FirstName { get; set; }
}
Which printed out:
Jane
John
I was facing the same Error with Order by.
I looked into the Call method and found that I was missing the Parameter -- list.Expression, where list is your IQuerable
Related
I need to dynamically execute the Include and Where method in it to build a query in EF. From the input parameters there is only the name of the field for Include and the condition in the form of a string.
What I want without dynamics looks like:
using System.Linq.Dynamic.Core;
contextEF.Table1.Include(x => x.Field1.Where("c => c.Id == 500")).ToList();
Here is my extreme attempt at getting this code to work dynamically:
var nameField = "Field1";
var queryWhere = "c => c.Id == 500";
var parameter = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(parameter, typeof(T).GetProperty(nameField));
var typeItem = typeof(T).GetProperty(nameField).PropertyType.GetGenericArguments().First();
var lambda = DynamicExpressionParser.ParseLambda(ParsingConfig.Default, typeItem, typeof(bool), queryWhere);
var expressionWhere = Expression.Call(typeof(Queryable), "Where", new Type[] { typeItem }, lambda, property);
var expressionInclude = Expression.Lambda<Func<T, object>>(expressionWhere, parameter);
return queryable.Include(expressionInclude);
At the moment, I get errors with a call to the Where method and I can't move further:
System.InvalidOperationException: No generic method 'Where' on type 'System.Linq.Queryable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.
It is clear from the error that it cannot find the Where method, where the parameter is string. But I don't understand how the program can prove that there is such a method =)
I managed to solve the problem, it all boiled down to the fact that I did not correctly specify the types for the .Include method, because there I have .Where, then the return type will be IEnumerable, and not ICollection as I expected.
var nameField = "Field1";
var queryWhere = "c => c.Id == 500";
var queryLambda = $"x => x.{nameField}.Where({queryWhere}).Take(20)";
Type typeEnumerable = typeof(IEnumerable<>).MakeGenericType(TProperty);
LambdaExpression lambdaInclude = DynamicExpressionParser.ParseLambda(typeof(T), typeEnumerable, queryLambda);
var includeQ =
Expression.Call(
type: typeof(Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions),
methodName: "Include",
typeArguments: new Type[] { typeof(T), typeEnumerable },
arguments: new[] {
queryable.Expression,
Expression.Quote(lambdaInclude)
}
);
queryable = queryable.Provider.CreateQuery<T>(includeQ);
I'm doing a little bit of search from a database using linq. I have multiple column names like country, name, phone number...
Now I've created a dropdownlist and pass the user selected data as a parameter "searchedField" to my controller method. Now if I take the input of a "country", I expect the code to be
entries = entries.Where(s => s.country.Contains(searchString));
If user selected "name"
entries = entries.Where(s => s.name.Contains(searchString));
Excuse me for this rather unreasonable example, since I can always just copy lines and make cases, but I wonder if there is a way to utilize things like reflection to convert string to "code" to access a field?
String searchedField = "name"
...
entries = entries.Where(s => s.searchedField.Contains(searchString));
This is my first question here, thanks!
You can use Dynamic Linq.
entries = entries
.Where(
string.Format(
"{0} = '{1}'",
searchedField,
searchString
)
);
Note: depending on the type of field you'll need to add quotes, or not.
You can do a reflection lookup (note: I've omitted error checking):
string GetPropertyAsString(object obj, string propertyName)
{
var propertyInfo - obj.GetType().GetProperty(propertyName);
return propertyInfo.GetValue(obj).ToString();
}
and then say
entries = entries.Where(s => GetPropertyAsString(s, searchedField).Contains(searchString));
You can build an expression tree for this (which will work with Linq to entities).
public static class QueryableExtensions {
public static IQueryable<T> Filter<T>(this IQueryable<T> queryable, string propertyName, string searchValue)
{
var type = typeof (T);
//this will be the left part of the lambda
var parameter = Expression.Parameter(type, "s");
//s.country for example
var property = Expression.Property(parameter, propertyName);
//string.Contains method
var containsMethod = typeof (string).GetMethod("Contains", new[] {typeof (string)});
//s.country.Contains(<yoursearchvalue>)
var expression = Expression.Call(property, containsMethod, Expression.Constant(searchValue));
//s => s.country.Contains(<yoursearchvalue>)
var lambda = Expression.Lambda<Func<T, bool>>(expression, parameter);
//filter your queryable with predicate
return queryable.Where(lambda);
}
}
usage
var fieldtosearch = "country";
entries = entries.Filter(fieldtosearch , searchString);
In general, other than using dynamic linq queries, you could also use Expression to build a generic method to construct a property Getter and use it in your linq query, a quick sampel:
public static Func<TObject, TProperty> GetPropGetter<TObject, TProperty>(string propertyName)
{
ParameterExpression paramExpression = Expression.Parameter(typeof(TObject), "value");
Expression propertyGetterExpression = Expression.Property(paramExpression, propertyName);
Func<TObject, TProperty> result =
Expression.Lambda<Func<TObject, TProperty>>(propertyGetterExpression, paramExpression).Compile();
return result;
}
to use it:
var getter = GetPropGetter<YourEntity, string>("Name");
var found = entites.Where(m => getter(m).Contains("someInput"));
I want to use Dynamic LINQ Query to Search with some text in all Properties in a class. i am using following function to create expression. I am passing property name and search text to the method.
In that method if the property type is String then it is working fine. if the property type is int, DateTime, GUID. then it is not working.
As we know Contains method only for array of elements or for string. I am thinking the value to property should type cast to string. So How to do it? Solution with Explanation is help full.
i Collected code from this.
public static Expression<Func<T, bool>> ContainsExp<T>(string propertyName, string contains)
{
var parameterExp = Expression.Parameter(typeof(T), "type");
var propertyExp = Expression.Property(parameterExp, propertyName);
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(contains, typeof(string));
var containsMethodExp = Expression.Call(propertyExp, method, someValue);
return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
}
Well, you probably know that's it's not possible to use ToString() in linq to entities.
So the following question is : how can I convert other types to string.
For numeric values, you have SqlFunctions.StringConvert, but it has only overloads for double? and decimal?
For DateTime, you may find something using SqlFunctions.StringConvert after having applied SqlFunctions.DatePart on your DateTime (which probably means at least 3 call to SqlFunctions.DatePart, for year, month, day)
For Guid, I don't think there's a way to do it directly. One way (at db level, if you use Sql Server) could be to have a Computed column. The computed column could store a varchar converted representation of your GUID. Maybe there's a better way.
Anyway, here's at least a sample which should work for integer as well as string:
public static Expression<Func<T, bool>> ContainsExp<T>(string propertyName, string contains)
{
//first, get the type of your property
var propertyType = typeof(T).GetProperty(propertyName).PropertyType;
//no change
var parameterExp = Expression.Parameter(typeof (T), "type");
Expression propertyExp = Expression.Property(parameterExp, propertyName);
//if property's type is int
if (propertyType == typeof (int))
{
//convert your Expression to a nullable double (or nullable decimal),
//so that you can use SqlFunctions.StringConvert
propertyExp = Expression.Convert(propertyExp, typeof (double?));
//get the SqlFunctions.StringConvert method for nullable double
var stringConvertMethod = typeof (SqlFunctions).GetMethod("StringConvert", new[] {typeof (double?)});
//call StringConvert on your converted expression
propertyExp = Expression.Call(stringConvertMethod , propertyExp);
}
//no change
var method = typeof (string).GetMethod("Contains", new[] {typeof (string)});
var someValue = Expression.Constant(contains, typeof (string));
var containsMethodExp = Expression.Call(propertyExp, method, someValue);
return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
}
public static IQueryable<T> FieldsContains<T>(this IQueryable<T> query, List<string> fileds, string searchValue)
{
Expression predicate = null;
var parameterExpression = Expression.Parameter(typeof(T), "type");
foreach (string field in fileds)
{
var next = GetFieldContainsExpression<T>(parameterExpression, field, searchValue);
if (predicate == null)
{
predicate = next;
}
else
{
predicate = Expression.Or(predicate, next);
}
}
var lambda = Expression.Lambda<Func<T, bool>>(predicate, parameterExpression);
return query.Where(lambda);
}
private static Expression GetFieldContainsExpression<T>(ParameterExpression parameterExpression, string field, string value)
{
var propertyType = typeof(T).GetProperty(field).PropertyType;
Expression propertyExpression = Expression.Property(parameterExpression, field);
var filterValue = Expression.Constant(value);
var method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
//call toString first to ignore type errors(datetime, int ...)
var toStringExpression = Expression.Call(propertyExpression, "ToString", Type.EmptyTypes);
var containsExpression = Expression.Call(toStringExpression, method, filterValue);
return containsExpression;
}
Given a class that has a property that is a Dictionary
public class Product
{
public Dictionary<string, string> Attributes { get { return attributes; } }
private Dictionary<string, string> attributes = new Dictionary<string, string>();
}
I want to be able to match products in a list of products based on criteria that are retrieved from a data store that are in the format of
Brand == Tyco
Color != Blue
My current approach is to construct an expression from the filter, and then pass that expression as the parameter to a LINQ Where method call like so
products = products.Where(myConstructedExpression);
where myConstructedExpression would normally be a lamda expression that looks like
p => p.Attributes[attribute] == value
I have assembled the following code for testing purposes, but it always fails the call to lambda.Compile() regardless of what I have tried for he left expression.
Dictionary<string, ExpressionType> expressionType = new Dictionary<string, ExpressionType>();
expressionType.Add("==", ExpressionType.Equal);
expressionType.Add("!=", ExpressionType.NotEqual);
string filter = "Brand == Tyco";
string[] fields = filter.Split(' ');
string attribute = fields[0];
string op = fields[1];
string value = fields[2];
Product product = new Product();
product.Attributes.Add("Brand", "Tyco");
var parameter = Expression.Parameter(typeof(Product), "p");
var left = /***** THIS IS WHAT I AM FAILING TO CONSTRUCT PROPERLY ********/
var right = Expression.Constant(value);
var operation = Expression.MakeBinary(expressionType[op], left, right);
var lambda = Expression.Lambda<Func<Product, bool>>(operation, parameter);
var result = lambda.Compile()(product);
Questions
Is this even a reasonable approach, and, if so,
How do I construct the left expression?
So to get p => p.Attributes["Brand"] <someoperator> "Tyco", you can do this.
The "trick", to work with indexed types, is to use their Item property (you could also work with the get_item method)
var parameter = Expression.Parameter(typeof(Product), "p");
Expression left = Expression.Property(parameter, "Attributes");
left = Expression.Property(left, "Item", new Expression[] { Expression.Constant(attribute) });
EDIT
the version with the IDictionary.ContainsKey(<value>) test
really step by step, but I think this makes things clearer at first.
//left part of lambda, p
var parameter = Expression.Parameter(typeof(Product), "p");
//right part
//p.Attributes
Expression left = Expression.Property(parameter, "Attributes");
var method = typeof(IDictionary<string, string>).GetMethod("ContainsKey");
//p.Attributes.ContainsKey("Brand");
Expression containsExpression = Expression.Call(left, method, Expression.Constant(attribute));
//p.Attributes.Item["Brand"]
Expression keyExpression= Expression.Property(left, "Item", new Expression[] { Expression.Constant(attribute) });
//"Tyco"
var right = Expression.Constant(value);
//{p => IIF(p.Attributes.ContainsKey("Brand"), (p.Attributes.Item["Brand"] == "Tyco"), False)}
Expression operation = Expression.Condition(
containsExpression,
Expression.MakeBinary(expressionType[op], keyExpression, right),
Expression.Constant(false));
var lambda = Expression.Lambda<Func<Product, bool>>(operation, parameter);
T is a type that may or may not have a specific property, lets say 'City'. For the types that have a property named 'City', I would like to restrict the records such that only residents of the Gotham are returned and they are sorted.
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, params object[] values)
{
var type = typeof(T);
var property = type.GetProperty(ordering);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExp = Expression.Lambda(propertyAccess, parameter);
MethodCallExpression resultExp = Expression.Call(
typeof(Queryable),
"OrderBy",
new Type[] { type, property.PropertyType },
source.Expression,
Expression.Quote(orderByExp));
string propertyToRestrictOn = "City";
string restrictedValue = "Gotham";
var restrictedProperty = type.GetProperty(propertyToRestrictOn);
if(null ! = restrictedProperty )
{
// TODO: What to add here so than only those records are returned that have a
// property named City and the value is 'Gotham'???
}
return source.Provider.CreateQuery<T>(resultExp);
}
if possible please name/link some helpful literature here as well just in case I have to create more complex queries i.e. mix And/OR
The code was borrowed from
How do I apply OrderBy on an IQueryable using a string column name within a generic extension method?
I'm not quite sure if I have understood you correctly, but I think I was in the same situation a few months ago.
The posted code here was my solution.
I think you should be especially interested in line 24.
Edit:
PropertyInfo p = ... // I used reflection in my examply to get properties with a certain Attribute
var expressionParameter = Expression.Parameter(typeof(SomeClass), "lambda");
var parameter = new [] { expressionParameter };
var propertyAccess = Expression.Property(expressionParameter, p);
var nullCheck = Expression.NotEqual(propertyAccess, Expression.Constant(null, p.PropertyType));
var nullCheckLambda = Expression.Lambda<Func<SomeClass, Boolean>>(nullCheck, parameter);
var containsMethodInfo = typeof(String).GetMethod("Contains", new[] { typeof(String) });
var contains = Expression.Call(propertyAccess, containsMethodInfo, Expression.Constant("ell"));
var containsLambda = Expression.Lambda<Func<SomeClass, Boolean>>(contains, new[] { expressionParameter });
var predicate = Expression.Lambda<Func<SomeClass, Boolean>>(
// line 24
Expression.AndAlso(nullCheckLambda.Body, containsLambda.Body), parameter);
Console.WriteLine(predicate.ToString());
I think you're making this harder than you have to. In the first part of your code (the OrderBy()), you don't actually need to generate the whole query expression, just the lambda inside it.
And in the second part (the optional Where()) you can do pretty much the same thing, just add Expression.Equal() and Expression.Constant():
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering)
{
var type = typeof(T);
var property = type.GetProperty(ordering);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
// necessary for value types to work
var cast = Expression.Convert(propertyAccess, typeof(object));
var orderByExp = Expression.Lambda<Func<T, object>>(cast, parameter);
IQueryable<T> result = source.OrderBy(orderByExp);
string propertyToRestrictOn = "City";
string restrictedValue = "Gotham";
var restrictedProperty = type.GetProperty(propertyToRestrictOn);
if (restrictedProperty != null)
{
var restrictionParameter = Expression.Parameter(type, "p");
var restrictionPropertyAccess =
Expression.MakeMemberAccess(restrictionParameter, restrictedProperty);
var restrictionEquality =
Expression.Equal(restrictionPropertyAccess,
Expression.Constant(restrictedValue));
var whereExp =
Expression.Lambda<Func<T, bool>>(restrictionEquality, restrictionParameter);
result = result.Where(whereExp);
}
return result;
}
Also, if your method is going to do more than just ordering, I think you shouldn't call it OrderBy().
You're half-way there already. You have the ordered expression already, so you're just going to use the Queryable.OrderBy expression call in a Queryable.Where expression (or vice-versa, doesn't really matter).
if(null != restrictedProperty )
{
var notEqualExp = Expression.NotEqual(parameter,
Expression.Constant(restrictedValue, typeof(string)));
resultExp = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { type },
resultExp,
Expression.Lambda(notEqualExp, parameter));
}
Haven't worked with building Expressions by hand in a while, so this is purely being done from memory. However, it should at least get you started and give you something to work with.
P.S. I would actually perform this check BEFORE the OrderBy method call. That way you end up with Queryably.Where(...).OrderBy(...) instead. But I guess if this is getting translated by the provider anyway, then shouldn't matter. However, just something I'd do to reduce any ambiguity of the generated query.