I need to sort in ajax response grid by column name. Column value is number stored as a string.
Let's say some trivial class (in real-life situation there is no possibility to modify this class):
class TestObject
{
public TestObject(string v)
{
this.Value = v;
}
public string Value { get; set; }
}
then simple test:
[Test]
public void LambdaConstructionTest()
{
var queryable = new List<TestObject>
{
new TestObject("5"),
new TestObject("55"),
new TestObject("90"),
new TestObject("9"),
new TestObject("09"),
new TestObject("900"),
}.AsQueryable();
var sortingColumn = "Value";
ParameterExpression parameter = Expression.Parameter(queryable.ElementType);
MemberExpression property = Expression.Property(parameter, sortingColumn);
//// tried this one: var c = Expression.Convert(property, typeof(double));
LambdaExpression lambda = Expression.Lambda(property, parameter); //// constructs: o=>o.Value
var callExpression = Expression.Call(typeof (Double), "Parse", null, property);
var methodCallExpression = Expression.Call(
typeof(Queryable),
"OrderBy",
new[] { queryable.ElementType, property.Type },
queryable.Expression,
Expression.Quote(lambda)); // works, but sorts by string values.
//Expression.Quote(callExpression)); // getting: System.ArgumentException {"Quoted expression must be a lambda"}
var querable = queryable.Provider.CreateQuery<TestObject>(methodCallExpression);
// return querable; // <- this is the return of what I need.
}
Sorry for not being clear in my first post as #SLaks answer was correct but I do not know how to construct correct lambda expression in this case.
Finally found solution that is good for anyone who has strings in column and needs to sort by converted double value: (Special thanks for #SLaks his post was an eye opener):
[Test]
public void LambdaConstructionTest2()
{
// GIVEN
var queryable = new List<TestObject>
{
new TestObject("5"),
new TestObject("55"),
new TestObject("90"),
new TestObject("9"),
new TestObject("09"),
new TestObject("900"),
}.AsQueryable();
var sortingColumn = "Value";
// WHEN
ParameterExpression parameter = Expression.Parameter(queryable.ElementType);
MemberExpression property = Expression.Property(parameter, sortingColumn);
MethodCallExpression callExpression = Expression.Call(typeof (Double), "Parse", null, property);
LambdaExpression lambda = Expression.Lambda(callExpression, parameter); // = {Param_0 => Parse(Param_0.Value)}
UnaryExpression unaryExpression = Expression.Quote(lambda); // Expression<Func<TestObject,double>> = {Param_0 => Parse(Param_0.Value)}
var methodCallExpression = Expression.Call(
typeof (Queryable),
"OrderByDescending",
new[] { queryable.ElementType, lambda.ReturnType },
queryable.Expression,
unaryExpression);
var querable = queryable.Provider.CreateQuery<TestObject>(methodCallExpression);
// THEN
var expectedMaxValue = queryable.Max(x => Convert.ToDouble(x.Value));
var expectedMinValue = queryable.Min(x => Convert.ToDouble(x.Value));
var list = querable.ToList();
var actualMaxValue = Convert.ToDouble(list.First().Value);
var actualMinValue = Convert.ToDouble(list.Last().Value);
Assert.AreEqual(expectedMaxValue, actualMaxValue);
Assert.AreEqual(expectedMinValue, actualMinValue);
}
You can call Expression.Call() to create an expression node that calls a method.
Expression.Call(typeof(Double), "Parse", null, property)
Related
I want to create an extension method for a LINQ expression but I'm stuck. What I need is just to create a method which will add a specific Where clause to a Queryable. Something like:
var hierarchy = "a string";
Session.Query<SomeClass>.Where(x => x.Layer.Hierarchy.StartsWith(hierarchy) ||
x.Layer.Hierarchy == hierarchy);
to become:
var hierarchy = "a string";
Session.Query<SomeClass>.LayerHierarchy(x => x.Layer, hierarchy);
And do that Where logic inside. So basicly the extension method LayerHierarchy() is running over the Queryable of T but the subject is of type Layer:
public static IQueryable<T> LayerHierarchy<T>(this IQueryable<T> query,
Expression<Func<T, Layer>> layer,
string hierarchy)
{
var parameterExp = Expression.Parameter(typeof(Layer), "layer");
var propertyExp = Expression.Property(parameterExp, "Hierarchy");
// StartWith method
MethodInfo methodStartsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
var valueStartsWith = Expression.Constant(string.Concat(hierarchy, "|"), typeof(string));
var methodExpStartsWith = Expression.Call(propertyExp, methodStartsWith, valueStartsWith);
var startsWith = Expression.Lambda<Func<Layer, bool>>(methodExpStartsWith, parameterExp);
// Equals method
MethodInfo methodEquals = typeof(string).GetMethod("Equals", new[] { typeof(string) });
var valueEquals = Expression.Constant(hierarchy, typeof(string));
var methodExpEquals = Expression.Call(propertyExp, methodEquals, valueEquals);
var equals = Expression.Lambda<Func<Layer, bool>>(methodExpEquals, parameterExp);
return query
.Where(startsWith)
.Where(equals);
}
Everything works fine above the return line. It complains that...
Cannot convert from System.Linq.Expressions.Expression<System.Func<Layer, bool>> to System.Linq.Expressions.Expression<System.Func<T, int, bool>>
when trying to pass the expressions to query.Where() method. How can I fix it?
Well, the problem is how you are creating the Lambdas. They should begin from T, not from Layer:
var startsWith = Expression.Lambda<Func<T, bool>>(methodExpStartsWith, parameterExp);
var equals = Expression.Lambda<Func<T, bool>>(methodExpEquals, parameterExp);
However, in order for this to work, you are missing one more PropertyExpression.
Your query now looks like:
(Layer)x => x.Hierarchy.StartsWith(...)
When, what you want is this:
(T)x => x.Layer.Hierarchy.StartsWith(...)
So, use this instead:
var parameterExp = Expression.Parameter(typeof(T), "item");
var layerExp = Expression.Property(parameterExp, "Layer");
var propertyExp = Expression.Property(layerExp, "Hierarchy");
Your logic should change a little though, since two .Where will generate an AND condition between them, and it seems like you want one of them to be true (StartsWith or Equals), so:
var parameterExp = Expression.Parameter(typeof(T), "item");
var layerExp = Expression.Property(parameterExp, "Layer");
var propertyExp = Expression.Property(layerExp, "Hierarchy");
// StartWith method
MethodInfo methodStartsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
var valueStartsWith = Expression.Constant(string.Concat(hierarchy, "|"), typeof(string));
var methodExpStartsWith = Expression.Call(propertyExp, methodStartsWith, valueStartsWith);
// Equals method
MethodInfo methodEquals = typeof(string).GetMethod("Equals", new[] { typeof(string) });
var valueEquals = Expression.Constant(hierarchy, typeof(string));
var methodExpEquals = Expression.Call(propertyExp, methodEquals, valueEquals);
var orElseExp = Expression.OrElse(methodExpStartsWith, methodExpEquals);
var orElse = Expression.Lambda<Func<T, bool>>(orElseExp, parameterExp);
return query.Where(orElse);
I need make custom orderby for enum. I try use SwitchExpression:
public static IQueryable<T> MyOrderByEnum<T>(this IQueryable<T> source, string propName, Type enumType)
{
var type = typeof (T);
var property = type.GetProperty(propName);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.Property(parameter, property);
var enumValues = Enum.GetValues(enumType);
var switchCases = new SwitchCase[enumValues.Length];
int i = 0;
foreach (var val in enumValues)
{
switchCases[i] = Expression.SwitchCase(
Expression.Constant(val.ToDisplay()),
Expression.Constant(val)
);
i++;
}
var switchExpr1 =
Expression.Switch(
propertyAccess,
Expression.Constant(""),
switchCases
);
var orderByExp1 = Expression.Lambda(switchExpr1, parameter);
MethodCallExpression resultExp = Expression.Call(typeof (Queryable), "OrderBy", new[] {type, orderByExp1.Body.Type}, source.Expression, orderByExp1);
return (IOrderedQueryable<T>) source.Provider.CreateQuery(resultExp);
}
But when I execute
filtered.MyOrderBy("field1", typeof(FieldState)).ToList();
I get error:
Unknown LINQ expression of type 'Switch'.
Is there another way to make order expression that will translate into sql construction "CASE WHEN ..."?
Try Expression.Condition (https://msdn.microsoft.com/en-us/library/bb340500%28v=vs.110%29.aspx)
I think that translates to CASE When if used in an anonymous projection
I am trying search each property value of an IQueryable collection of T against the value of a search query. I have the following function and would like to know how do I ALSO test for NOT NULL and CONTAINS together?
private Expression<Func<T, bool>> PropertySearch
{
get
{
// Object that is passed to the lambda expression
ParameterExpression instance = Expression.Parameter(typeof(T), "val");
Expression whereExpr = Expression.Constant(true); // default is val => True
var _properties = typeof(T).GetProperties();
foreach (var prop in _properties)
{
var query = _httpRequest["query"].ToLower();
var property = Expression.Property(instance, prop);
var toStringCall = Expression.Call(Expression.Call(
property,
"ToString",
new Type[0]),
typeof(string).GetMethod("ToLower", new Type[0]));
whereExpr = Expression.And(whereExpr,
Expression.Call(toStringCall, typeof(string).GetMethod("Contains"),
Expression.Constant(query)));
}
return Expression.Lambda<Func<T, bool>>(whereExpr, instance);
}}
I have created a search extensions nuget package that performs this type of check. For your example I would do something like the following.
Note, this is without an IDE so may have some errors
/* *** Start: These can be made private reaonly fields ***/
var comparisonExpr = Expression.Constant(StringComparison.OrdinalIgnoreCase);
var zeroExpression = Expression.Constant(0)
var nullExpression = Expression.Constant(null)
MethodInfo IndexOfMethod = typeof(string).GetMethod("IndexOf", new[] { typeof(string), typeof(StringComparison) });
/* *** End ***/
Expression finalExpression = null
ParameterExpression instance = Expression.Parameter(typeof(T), "val");
var _properties = typeof(T).GetProperties();
var query = _httpRequest["query"].ToLower();
var queryExpr = Expression.Constant(query);
foreach (var prop in _properties)
{
//Get property
var propertyExpr = Expression.Property(instance, prop);
//Get property as string
var propStringExpr = Expression.Call(property, "ToString", new Type[0]);
//Perform IndexOf call
var indexOfExpr = Expression.Call(propStringExpr,
IndexOfMethod,
queryExpr,
comparisonExpr);
// Check index of is greater than or equal to zero
var containsExpr = Expression.GreaterThanOrEqual(containsExpr, zeroExpression);
if(finalExpression == null)
{
finalExpression = containsExp;
}
else
{
finalExpression = Expression.AndAlso(containsExpr);
}
}
return Expression.Lambda<Func<T, bool>>(finalExpression, instance);
I've removed the need for ToLower() and instead used IndexOf with a string comparison type
If you want to see how I have achieved similar functionality, take a look at NinjaNye.SearchExtensions on Github
https://github.com/ninjanye/SearchExtensions
If you wanted to search a collection of IQueryable you could use NinjaNye.SearchExtensions as follows
string query = _httpRequest["query"];
var result = data.SearchAll().Containing(query);
This will search all string properties (not all properties as you have above) and return just those where any property mathes the search term.
Hope this helps
You could probably use PredicateBuilder so you don't have to mess with expression trees yourself.
Look the code below. I'd like to replace USERNAME by the field name received in the parameter field. This method must be able to make some search on several fields.
Thank,
public void Searching(string field, string stringToSearch)
{
var res =
from user in _dataContext.USERs where
user.USERNAME.Contains(stringToSearch)
select new
{
Id = user.ID,
Username = user.USERNAME
};
}
You need to forget about the anonymous type, maybe use Tuple<int,string> instead; but: how about:
IQueryable<Foo> source = // YOUR SOURCE HERE
// in-memory dummy example:
// source = new[] {
// new Foo {Id = 1, Bar = "abc"},
// new Foo {Id = 2, Bar = "def"}
// }.AsQueryable();
string field = "Bar";
string stringToSearch = "d";
var param = Expression.Parameter(typeof (Foo), "x");
var predicate = Expression.Lambda<Func<Foo, bool>>(
Expression.Call(
Expression.PropertyOrField(param, field),
"Contains", null, Expression.Constant(stringToSearch)
), param);
var projection = Expression.Lambda<Func<Foo, Tuple<int, string>>>(
Expression.Call(typeof(Tuple), "Create", new[] {typeof(int), typeof(string)},
Expression.PropertyOrField(param, "Id"),
Expression.PropertyOrField(param, field)), param);
Tuple<int,string>[] data = source.Where(predicate).Select(projection).ToArray();
As a matter of fact it is possible using the Expression API:
public void Searching(Expression<Func<User,string>> field, string stringToSearch)
{
var call = Expression.Call(field.Body, typeof (string).GetMethod("Contains"), new[] {Expression.Constant(value)});
Expression<Func<User, bool>> exp = Expression.Lambda<Func<User, bool>>(Expression.Equal(call, Expression.Constant(true)), field.Parameters);
var res = _dataContext.USERs.Where(exp).Select(u=>new { id= u.ID, Username = u.USERNAME});
}
What you are trying is not possible. You can however use the dynamic linq library to achieve what you want
i want to build a generic search window using linq to sql.
This is what i was trying to do:
class SearchWindow<T> : Form : Where T: class
{
public SearchWindow(Func<T, string> codeSelector,
Func<T, string> nameSelector)
{
var db = new DataContext();
var table = db.GetTable<T>();
var query = from item in table where
codeSelector(item).Contains(someText) &&
nameSelector(item).Contains(someOtherText)
select item;
}
}
And i was trying to use it like:
var searchWindow = new SearchWindow<SomeTable>(x => x.CodeColumn,
y => y.NameColumn).Show();
Bud saddly that doesn't work, i read about expression trees so i tried to do that with them, and i got:
public SearchWindow(codeColumn, nameColumn)
{
Table<T> table = db.GetTable<T>();
var instanceParameter = Expression.Parameter(typeof(T), "instance");
var methodInfo = typeof(string).GetMethod("Contains",
new Type[] { typeof(string) });
var codigoExpression = Expression.Call(Expression.Property(instanceParameter,
codeColumn),
methodInfo,
Expression.Constant("someText",
typeof(string)));
var nombreExpression = Expression.Call(Expression.Property(instanceParameter,
nameColumn),
methodInfo,
Expression.Constant("someOtherText",
typeof(string)));
var predicate = Expression.Lambda<Func<T, bool>>(
Expression.And(codigoExpression, nombreExpression), instanceParameter);
var query = table.Where(predicate);
}
And to use it i need to do:
new SearchWindow<SomeTable>("codeColumn", "nameColumn");
But i don't like the approach to need to enter the column names as a string, is there any way to do it in a fashion similar to my first approach (in order to have intellisense and strong typing)?
Thank you for your help.
Untested, but something like:
static IQueryable<T> Search<T>(
IQueryable<T> source,
Expression<Func<T, string>> codeSelector,
Expression<Func<T, string>> nameSelector,
string code, string name)
{
var row = Expression.Parameter(typeof(T), "row");
var body = Expression.AndAlso(
Expression.Call(
Expression.Invoke(codeSelector, row),
"Contains", null,
Expression.Constant(code, typeof(string))),
Expression.Call(
Expression.Invoke(nameSelector, row),
"Contains", null,
Expression.Constant(name, typeof(string))));
var lambda = Expression.Lambda<Func<T, bool>>(body, row);
return source.Where(lambda);
}
You pass in your table (GetTable<T>) as the source, and lambdas to indicate the columns (x => x.CodeColumn / y => y.NameColumn etc).
Update; tested on LINQ-to-Objects, I'm hopeful it'll work on LINQ-to-SQL as well:
var data = new[] {
new { Code = "abc", Name = "def"},
new { Code = "bcd", Name = "efg"},
new { Code = "ghi", Name = "jkl"}
}.AsQueryable();
var filtered = Search(data, x => x.Code, x => x.Name, "b", "f");
var arr = filtered.ToArray();
Use PredicateBuilder- it'll do the heavy lifting for you.