Linq Sort Direction From String - c#

I am trying to sort a set of Users. I have access to the sorting property and direction (asc, desc). My current order by query is below. But as you can see it doesn't account for the sort direction. How do can I build this expression without having to use Dynamic Linq, or adding another set of statements for "asc" or "desc" sort direction.
public override IQueryable<DalLinq.User> GetSort(IQueryable<DalLinq.User> query)
{
//SelectArgs.SortDirection <- Sort Direction
switch (SelectArgs.SortProperty)
{
case "LastName":
query = query.OrderBy(p => p.LastName);
break;
case "FirstName":
query = query.OrderBy(p => p.FirstName);
break;
default:
query = query.OrderBy(p => p.UserName);
break;
}
return query;
}

Ideally, you want to use OrderByDescending - you could of course cheat:
public static class MyExtensionMethods
{
public static IOrderedQueryable<TSource> OrderBy<TSource,TValue>(
this IQueryable<TSource> source,
Expression<Func<TSource,TValue>> selector,
bool asc)
{
return asc ? source.OrderBy(selector) : source.OrderByDescending(selector);
}
}
And use OrderBy passing in the selector and a bool?
If you don't need the static typing, you can also build the expressions dynamically from the ground up, of course - like this short sample (similar in nature to the dynamic LINQ library).

It'd be an if statement I think, no other simple way to do it, i.e.:
query = (SelectArgs.SortDirection == "asc") ? query.OrderBy(p => p.LastName)
: query.OrderByDescending(p => p.LastName);
Have a look at this as well: Sorting a list using Lambda/Linq to objects

Have a look at the CS Code Samples. There are a dynamic Linq examples.
From the samples:
Northwind db = new Northwind(connString);
db.Log = Console.Out;
var query =
db.Customers.Where("City == #0 and Orders.Count >= #1", "London", 10).
OrderBy("CompanyName").
Select("New(CompanyName as Name, Phone)");
Order by Code:
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, params object[] values) {
return (IQueryable<T>)OrderBy((IQueryable)source, ordering, values);
}
public static IQueryable OrderBy(this IQueryable source, string ordering, params object[] values) {
if (source == null) throw new ArgumentNullException("source");
if (ordering == null) throw new ArgumentNullException("ordering");
ParameterExpression[] parameters = new ParameterExpression[] {
Expression.Parameter(source.ElementType, "") };
ExpressionParser parser = new ExpressionParser(parameters, ordering, values);
IEnumerable<DynamicOrdering> orderings = parser.ParseOrdering();
Expression queryExpr = source.Expression;
string methodAsc = "OrderBy";
string methodDesc = "OrderByDescending";
foreach (DynamicOrdering o in orderings) {
queryExpr = Expression.Call(
typeof(Queryable), o.Ascending ? methodAsc : methodDesc,
new Type[] { source.ElementType, o.Selector.Type },
queryExpr, Expression.Quote(Expression.Lambda(o.Selector, parameters)));
methodAsc = "ThenBy";
methodDesc = "ThenByDescending";
}
return source.Provider.CreateQuery(queryExpr);
}
But be sure that you check User Input!

Related

Linq Dynamic Query OrderBy With Navigation Property

I have this method bellow to build a dynamic linq query with pagination and sort. But when a send to sort by a property that has relation with other entity like Entity Product has relation with Customer and a call the method to get Customer and in "includeProperties" send Product and i want sort by "Product.Name". it didn't works
The Method:
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "", int page = 0, int pagesize = 100, string sortColumn = "", string sortColumnDir = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (sortColumn != string.Empty && sortColumn.IndexOf(".") <= 0)
{
ParameterExpression[] typeParams = new ParameterExpression[] {
Expression.Parameter(typeof(TEntity), "")
};
System.Reflection.PropertyInfo pi = typeof(TEntity).GetProperty(sortColumn);
sortColumnDir = sortColumnDir == "asc" ? "OrderBy" : "OrderByDescending";
query = (IOrderedQueryable<TEntity>)query.Provider.CreateQuery(
Expression.Call(
typeof(Queryable),
sortColumnDir,
new Type[] { typeof(TEntity), pi.PropertyType },
query.Expression,
Expression.Lambda(Expression.Property(typeParams[0], pi), typeParams))
);
}
else if (orderBy != null)
{
query = orderBy(query);
}
else
{
query = query.OrderBy(obj => obj.Desativado);
}
query = query.Skip(page * pagesize).Take(pagesize);
return query.ToList();
}
Anyone can help me?
What does your query builder method do when you pass something like "X.Y" in the sortColumn parameter?
First it tries
if (sortColumn != string.Empty && sortColumn.IndexOf(".") <= 0)
The condition evaluates to false as you have a dot in sortColumn. So this branch is skipped.
Then comes
else if (orderBy != null)
This isn't a hit again, you didn't supply the orderBy but the sortColumn argument.
So finally you end up with
else
{
query = query.OrderBy(obj => obj.Desativado);
}
And this is obviously far from what you wanted to achieve as it won't sort by X.Y at all.
What to do then? You have to options:
You define sorting using callback instead of a path string:
ProductRepository.Get(p => p.Name == "X", includeProperties: "Item", orderBy: query => query.OrderBy(product => product.Item.Codigo))
You change the query builder method so that it accepts a property path in sortColumn. This would need somewhat more effort, though.

Generic predicate to implement searching by any string property

Is there possibility to apply searching by any string property of entity? I need to build predicate to use it in LINQ query to database.
var stringPropertyNames = typeof(T)
.GetProperties()
.Where(pi => pi.PropertyType == typeof(string) && pi.GetGetMethod() != null)
.Select(pi => pi.Name);
foreach (var item in stringPropertyNames)
{
// here some code to build Predicate corresponding to sql LIKE statement
}
UPD:
here is working code:
public static IEnumerable<T> ContainsInAnyString<T>(IQueryable<T> collection, string query)
{
var stringPropertyNames = typeof(T)
.GetProperties()
.Where(pi => pi.PropertyType == typeof(string) && pi.GetGetMethod() != null)
.Select(pi => pi.Name);
var item = Expression.Parameter(typeof(T), "item");
var searchArgument = Expression.Constant(query);
var method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
foreach (var name in stringPropertyNames)
{
var currentProp = Expression.Property(item, name);
var startsWithDishExpr = Expression.Call(currentProp, method, searchArgument);
var lambda = Expression.Lambda<Func<T, bool>>(startsWithDishExpr, item);
collection = collection.Where(lambda);
}
return collection;
}
Hope it would be helpful for someone.
It really is rather simple:
var entityParameter = Expression.Parameter(typeof(T), "entity");
return
Expression.Lambda<Func<T, bool>>
(
Expression.Equal
(
Expression.MakeMemberAccess(entityParameter, propertyInfo),
Expression.Constant(valueToSearchBy)
),
entityParameter
);
Of course, this does A == B. If you want A like '%' + B + '%' instead, you'll have to change the Expression.Equal to string.Contains. I'll leave that as an excercise, since you haven't shown any of your work yet :)

how can I extend dlinq class to have empty strings last in a sort [duplicate]

I try to make my custom orderby extension method, i successfully worked my code but in addition i want to list null or empty or zero values last in result, anyone can help me about that issue ?
Here is my extension method to orderby
public static IQueryable<T> OrderBy<T>(this IQueryable<T> q, string SortField, bool isAsc)
{
//var nullExpr = Expression.Constant(null, typeof(T));
var param = Expression.Parameter(typeof(T), "p");
var prop = Expression.Property(param, SortField);
var exp = Expression.Lambda(prop, param);
string method = isAsc ? "OrderBy" : "OrderByDescending";
Type[] types = new Type[] { q.ElementType, exp.Body.Type };
var mce = Expression.Call(typeof(Queryable), method, types, q.Expression, exp);
return q.Provider.CreateQuery<T>(mce);
}
Thanks in advance
The simplest way is to use
OrderBy(e => String.IsNullOrEmpty(e.TeamName)
This doesn't require any extension method or custom IComparer implementation etc.
var entries = repository.Race.Where(e => e.EventId == id)
.OrderBy(e => String.IsNullOrEmpty(e.TeamName))
.ThenBy(e => e.LastName)
.ThenBy(e => e.FirstName);
Without using an extension method....
Create a custom IComparer<string> to check the empty values before using the default String.Compare. The first checks will return -1 instead of 1 or 1 instead of -1, if using the standard string comparison.
/// <summary>
/// Returns -1 instead of 1 if y is IsNullOrEmpty when x is Not.
/// </summary>
public class EmptyStringsAreLast : IComparer<string>
{
public int Compare(string x, string y)
{
if (String.IsNullOrEmpty(y) && !String.IsNullOrEmpty(x))
{
return -1;
}
else if (!String.IsNullOrEmpty(y) && String.IsNullOrEmpty(x))
{
return 1;
}
else
{
return String.Compare(x, y);
}
}
}
Pass your EmptyStringsAreLast comparer into the OrderBy of Lambda expression. In this solution teams who have entered the race should appear alphabetical order, but the unaffiliated race entries should appear at then end.
var entries = repository.Race.Where(e => e.EventId == id)
.OrderBy(e => e.TeamName, new EmptyStringsAreLast())
.ThenBy(e => e.LastName)
.ThenBy(e => e.FirstName);
This answer is perhaps what you were originally looking for - using your generic extension method:
public static IQueryable<T> OrderByFieldNullsLast<T>(this IQueryable<T> q, string SortField, bool Ascending)
{
//We are rebuilding .OrderByDescending(p => p.SortField.HasValue).ThenBy(p => p.SortField)
//i.e. sort first by whether sortfield has a value, then by sortfield asc or sortfield desc
//create the expression tree that represents the generic parameter to the predicate
var param = Expression.Parameter(typeof(T), "p");
//create an expression tree that represents the expression p=>p.SortField.HasValue
var prop = Expression.Property(param, SortField);
var hasValue = Expression.Property(prop, "HasValue");
var exp = Expression.Lambda(hasValue, param);
string method = "OrderByDescending";
Type[] types = new Type[] { q.ElementType, exp.Body.Type };
var orderByCallExpression = Expression.Call(typeof(Queryable), method, types, q.Expression, exp);
//now do the ThenBy bit,sending in the above expression to the Expression.Call
exp = Expression.Lambda(prop, param);
types = new Type[] { q.ElementType, exp.Body.Type };
method = Ascending ? "ThenBy" : "ThenByDescending";
var ThenByCallExpression = Expression.Call(typeof(Queryable), method, types,orderByCallExpression, exp);
return q.Provider.CreateQuery<T>(ThenByCallExpression);
}
Building on Dave Anson's answer, you can user Comparer.Create() to create the Comparer from a lambda. Here's an example that sorts unsorted by its myString string fields, with null or empty strings appearing last.
var sorted = unsorted.OrderBy(x => x.myString, Comparer<string>.Create((x, y) => {
if ( string.IsNullOrEmpty(y) && !string.IsNullOrEmpty(x)) return -1;
else if (!string.IsNullOrEmpty(y) && string.IsNullOrEmpty(x)) return +1;
else return string.Compare(x, y);
}))
(To put them first, switch the signs on the 1 constants)
it works for me:
private static IQueryable<T> GetOrderQuery<T>(this IQueryable<T> q, BaseFilterCollection filter)
{
q = q.OrderBy(GetExpression<T>(filter.SortField));
var param = Expression.Parameter(typeof(T), "p");
var prop = Expression.Property(param, filter.SortField);
var exp = Expression.Lambda(prop, param);
string method = filter.SortDirection == SortDirectionType.Asc ? "ThenBy" : "ThenByDescending";
Type[] types = { q.ElementType, exp.Body.Type };
var rs = Expression.Call(typeof(Queryable), method, types, q.Expression, exp);
return q.Provider.CreateQuery<T>(rs);
}
private static Expression<Func<T, bool>> GetExpression<T>(string sortField)
{
ParameterExpression param = Expression.Parameter(typeof(T), "p");
Expression prop = Expression.Property(param, sortField);
var info = typeof(T).GetProperty(sortField, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
Expression exp = Expression.Equal(prop, info.PropertyType.IsValueType
? Expression.Constant(Activator.CreateInstance(info.PropertyType))
: Expression.Constant(null));
return Expression.Lambda<Func<T, bool>>(exp, param);
}
You dont need to complicate, the easiest way is to do something like this:
YourList.OrderByDescending(x => string.IsNullOrEmpty(x.value)
Use OrderByDescending or OrderBy depending on if you want to see empty strings in the beginning or last.
Regards

Understanding Expression Tree and Parameter Evaluation

I'm attempting to modify an expression tree that dynamically builds a Contains expression that ultimately results in SQL like
P IN (123, 124, 125, 200, 201)
to instead check perform range checks, which ultimately results in SQL like
(P >= 123 AND P <= 125) OR (P >= 200 AND P <= 201)
I'm basing my solution on this post.
static public Expression<Func<TElement, bool>>
BuildContainsExpression<TElement, TValue>(
Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values)
{
// Removed for post: Input checks and edge cases
var equals =
values.Select(value =>
(Expression)Expression.Equal(valueSelector.Body,
Expression.Constant(value, typeof(TValue))));
var body = equals.Aggregate<Expression>((accumulate, equal) =>
Expression.Or(accumulate, equal));
return Expression.Lambda<Func<TElement, bool>>(body, p);
}
I'm able to get the range checking to work if I provide the value for comparison:
long testValue = 5;
List<KeyValuePair<int, int>> ranges = new List<KeyValuePair<int, int>>()
{
new KeyValuePair<long, long>(3, 6),
new KeyValuePair<long, long>(10, 12),
new KeyValuePair<long, long>(20, 20),
};
List<BinaryExpression> rangeExpressions = new List<BinaryExpression>();
foreach (var pair in ranges)
{
var greaterThanOrEqual =
Expression.GreaterThanOrEqual(Expression.Constant(testValue),
Expression.Constant(pair.Key));
var lessThanOrEqual =
Expression.LessThanOrEqual(Expression.Constant(testValue),
Expression.Constant(pair.Value));
var inRange = Expression.AndAlso(greaterThanOrEqual, lessThanOrEqual);
rangeExpressions.Add(inRange);
}
var final =
rangeExpressions.Aggregate<Expression>((a, b) => Expression.Or(a, b));
var result = Expression.Lambda<Func<bool>>(final).Compile()();
However, I cannot sort out how to get the value for comparison from the passed-in expression when I drop that code into the method to be used with Linq. The signature of that method is:
Expression<Func<TElement, bool>>
BuildRangeExpression<TElement>(
Expression<Func<TElement, long>> valueSelector,
IEnumerable<long> values)
and it is used like:
Expression<MyType, bool> match =
BuildRangeExpression<MyType, long>(my => my.ProductCode, productCodes);
var result = db.MyTypes.Where(match);
QUESTION
How can I evaluate
Expression<Func<TElement, long>> valueSelector
so that I can use the value passed into BuildRangeExpression instead of my currently hard-coded value
long testValue = 5;
I think the code from the blog post has exactly what you need: all you have to do is to use valueSelector.Body instead of your Expression.Constant() and also add the original parameter to the generated expression:
public static Expression<Func<TElement, bool>>
BuildRangeExpression<TElement, TValue>(
Expression<Func<TElement, TValue>> valueSelector,
IEnumerable<Tuple<TValue, TValue>> values)
{
var p = valueSelector.Parameters.Single();
var equals = values.Select(
tuple =>
Expression.AndAlso(
Expression.GreaterThanOrEqual(
valueSelector.Body, Expression.Constant(tuple.Item1)),
Expression.LessThanOrEqual(
valueSelector.Body, Expression.Constant(tuple.Item2))));
var body = equals.Aggregate(Expression.OrElse);
return Expression.Lambda<Func<TElement, bool>>(body, p);
}
Use Expression.Parameter.
Create a parameter:
var param = Expression.Parameter(typeof(TElement), "arg")
Instead of Expression.Constant(testvalue), you will need to put param.
Then, you need to do:
var result = Expression.Lambda<Func<TElement, bool>>(final, param).Compile()

Dynamically construct "or" LIKE query in LINQ to SQL

I have a LINQ query which is composed by an anonymous object.
At a given point, I want to limit the results by incoming search parameters, but this can be one or more parameters, and I want to perform a "LIKE x OR LIKE y OR LIKE z" using those.
In code, it would look like this:
reservations = reservations.Where(r =>
r.GuestLastName.Contains(parameter1) || r.GuestFirstName.Contains(parameter1) ||
r.GuestLastName.Contains(parameter2) || r.GuestFirstName.Contains(parameter2) ||
// Parameter 3, 4, 5,..
);
How could I construct this dynamically, knowing that reservations is of the type IQueryable<'a> (anonymous object)? I've looked around on various resources and I can only seem to find a way to do it when I know the type, not when using anonymous types.
It's important to know that it's Linq to SQL, so it should be translated to an SQL query and not be filtered in memory...
There are two possible ways:
Building an Expression, as pointed out by Coincoin
Putting all your parameters into an array and using Any:
var parameters = new [] { parameter1, parameter2, /*...*/ }
reservations = reservations
.Where(r =>
parameters.Any(p => r.GuestFirstName.Contains(p)
|| r.GuestLastName.Contains(p)));
I would write my own generic extension method:
public static class CollectionHelper
{
public static IQueryable Filter<T>(this IQueryable source, string[] properties, string[] values)
{
var lambda = CombineLambdas<T>(properties, values);
var result = typeof (Queryable).GetMethods().First(
method => method.Name == "Where"
&& method.IsGenericMethodDefinition)
.MakeGenericMethod(typeof (T))
.Invoke(null, new object[] {source, lambda});
return (IQueryable<T>) result;
}
// combine lambda expressions using OR operator
private static LambdaExpression CombineLambdas<T>(string[] properties, string[] values)
{
var param = Expression.Parameter(typeof (T));
LambdaExpression prev = null;
foreach (var value in values)
{
foreach (var property in properties)
{
LambdaExpression current = GetContainsExpression<T>(property, value);
if (prev != null)
{
Expression body = Expression.Or(Expression.Invoke(prev, param),
Expression.Invoke(current, param));
prev = Expression.Lambda(body, param);
}
prev = prev ?? current;
}
}
return prev;
}
// construct expression tree to represent String.Contains
private static Expression<Func<T, bool>> GetContainsExpression<T>(string propertyName, string propertyValue)
{
var parameterExp = Expression.Parameter(typeof (T), "type");
var propertyExp = Expression.Property(parameterExp, propertyName);
var method = typeof (string).GetMethod("Contains", new[] {typeof (string)});
var someValue = Expression.Constant(propertyValue, typeof (string));
var containsMethodExp = Expression.Call(propertyExp, method, someValue);
return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
}
}
and the usage:
var reservations = new List<TheType>() // sample collection
{
new TheType {FirstName = "aa", LastName = "bb"},
new TheType {FirstName = "cc", LastName = "dd"},
new TheType {FirstName = "ee", LastName = "ff"}
}.AsQueryable();
var filtered = reservations
.Filter<TheType>(new[] {"FirstName", "LastName"}, new[] {"d", "e"});
/* returnes 2 elements:
* {FirstName = "cc", LastName = "dd"} and {FirstName = "ee", LastName = "ff"} */
I don't know a general solution you'd like to have - if exists any, but I hope it can be acceptable alternative which solves your case by building desired filter dynamically.
I found the solution after some debugging, but I create a WhereFilter with multiple selectors, one for FirstName and one for LastName..
This is the extension method:
public static IQueryable<T> WhereFilter<T>(this IQueryable<T> source, string[] possibleValues, params Expression<Func<T, string>>[] selectors)
{
List<Expression> expressions = new List<Expression>();
var param = Expression.Parameter(typeof(T), "p");
var bodies = new List<MemberExpression>();
foreach (var s in selectors)
{
bodies.Add(Expression.Property(param, ((MemberExpression)s.Body).Member.Name));
}
foreach (var v in possibleValues)
{
foreach(var b in bodies) {
expressions.Add(Expression.Call(b, "Contains", null, Expression.Constant(v)));
}
}
var finalExpression = expressions.Aggregate((accumulate, equal) => Expression.Or(accumulate, equal));
return source.Where(Expression.Lambda<Func<T, bool>>(finalExpression, param));
}
It can be used like this:
reservations = reservations.WhereFilter(
array_of_allowed_values,
r => r.GuestFirstName,
r => r.GuestLastName
);
I checked the trace string of the query and it actually translated to SQL, so the filtering is performed at the database.

Categories