Use multiple ThenBy directly without any order - c#

I'm trying to sort based on multiple columns simultaneously in LINQ.
To achieve to this sorted list I should use SortBy for the first column and then multiple ThenBy to sort the result of OrderBy by another column.
But the problem is that I don’t have any order in using ThenBy(s) and that's because user chooses the first column and I use this column as parameter to OrderBy and the rest of columns are parameters to ThenBy.
So I first, declare a dynamic global variable like :
dynamic result;
Then create an entity model like :
DatabaseEntityContext context = new DatabaseEntityContext();
And query data to get an adequate Anonymous Data Type :
var query1 = db.context.Select(x => new { Column1 = x.Column1, Column2 = x.Column2,
Column3 = x.Column3, Column4 = x.Column4, Column5 = x.Column5 }).ToList();
var query2 = query1.Select(x => new { Column1 = x.Column1, Column2 = x.Column2,
Column3 = x.Column3, TotalColumn = x.Column4 + "-" + x.Column5 }).ToList();
And finally assign query2 to result like :
result = query2;
To use LINQ functions on result I cast it to ((IEnumerable<dynamic>)result)
but the thing is the result of this cast doesn't have any ThenBy extension and I can't use OrderBy first because the list may already been sorted by another column and I should use a ThenBy to sort it again based on the result of previously sorted list.
My problem is, I have multi factors to use in ThenBy but I can’t sort this because I should OrderBy first,then use ThenBy after OrderBy and I can’t use ThenBy directly.
So how can I use ThenBy directly with a previously ordered list ?!
Update 1 :
According to #Patrick Hofman I changed my cast type to IOrderedQueryable like :
result = ((IOrderedQueryable<dynamic>)result).ThenBy(x => x.MyDynamicField).ToList();
but no it gives me a compile error on "x.MyDynamicField" :
An expression tree may not contain a dynamic operation
I also test IOrderedEnumerable but it gives InvalidCastException error :
Unable to cast object of type
'System.Collections.Generic.List1[<>f__AnonymousType15[System.String,System.String,System.DateTime,System.TimeSpan,System.Nullable1[System.Int32]]]'
to type 'System.Linq.IOrderedEnumerable1[System.Object]'.

ThenBy is an extension method on IOrderedQueryable, not IEnumerable. If you want to call ThenBy, you should cast it to an IOrderedQueryable:
var r = ((IOrderedQueryable<dynamic>)result).ThenBy(...);
As juharr commented, you can even expand this to check the type and then chose either ThenBy or OrderBy, using the same predicate to filter on.

Instead of using OrderBy/ThenBy you could:
public static class QueryableOrderBy
{
public static IOrderedQueryable<TSource> OrderBySimple<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector)
{
var ordered = source as IOrderedQueryable<TSource>;
if (source != null)
{
return ordered.ThenBy(keySelector);
}
return source.OrderBy(keySelector);
}
public static IOrderedQueryable<TSource> OrderByDescendingSimple<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector)
{
var ordered = source as IOrderedQueryable<TSource>;
if (source != null)
{
return ordered.ThenByDescending(keySelector);
}
return source.OrderByDescending(keySelector);
}
}
These methods will "analyze" if there is already another OrderBy (then they will use ThenBy) or not.

Related

Custom Extension Method in Linq-EF Equivalent to SQL "IN" Clause

I am trying to create a custom Linq to Entities extension method which takes a comma-delimited string, converts it to an array, then using IEnumerable<string>.Contains to generate the equivalent of a SQL IN clause.
Easy enough when you always know the table/entity and its column/property that you want to apply this filter to. The challenge is that I want to be able to use this extension method on any entity or property.
This is how far I've come:
public static IQueryable<TSource> CustomInClause<TSource>(this IQueryable<TSource> myQuery, Expression<Func<TSource, string>> colExpression, string filterCriteria)
{
string[] myArray = filterCriteria.Split(",", StringSplitOptions.RemoveEmptyEntries);
//Various other operations here..............
if (myArray.Length > 0)
{
myQuery = myQuery.Where(b => myArray.Contains(colExpression));
}
return myQuery;
}
As you can see, I am trying to use colExpression as a dynamic expression which will be the equivalent of x => x.SomeColumn where SomeColumn could be any string/varchar column.
I would then implement this extension like this:
var q = context.SomeTable.CustomInClause(f => f.SomeColumn, someString);
var q2 = context.OtherTable.CustomInCluse(f => f.OtherColumn, otherString);
Right now I get this error:
'string[]' does not contain a definition for 'Contains' and the best
extension method overload
'ParallelEnumerable.Contains>>(ParallelQuery>>,
Expression>)' requires a receiver of type
'ParallelQuery>>'
I'm not quite sure how to use a parallel query in this instance, or if there is another solution. Any ideas?
You have to build Contains call as part of expression in where clause
var myArray = filterCriteria.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)
.ToList();
var containsExp = Expression.Call(Expression.Constant(myArray),
"Contains", null, colExpression.Body);
if (myArray.Count > 0)
{
myQuery = myQuery.Where(Expression.Lambda<Func<TSource, bool>>
(containsExp, colExpression.Parameters));
}
return myQuery;
List is better than Array in this case, because list has Contains function and Array has only extension

LINQ Entity Framework Variable to select db Column

does anyone know how do I query a variable to select a column name of a table? Example is given as below. Instead of Select(x => x.ColumnName), I would like to Select(field).
public bool isFound(string field, int id)
{
db.Table.Where(x => x.tableID == id).Select(field).First();
return;
}
Try this,
string field = "tableID";
ParameterExpression param = Expression.Parameter(typeof(Table), "x");
MemberExpression propExpression = Expression.PropertyOrField(param, field);
Expression<Func<Table, string>> selector = Expression.Lambda<Func<Table, string>>(propExpression, param);
var result = db.Table.Select(selector).First();
Or Use Nuget package DotNetHelper - https://www.nuget.org/packages/DotNetHelper/
Install-Package DotNetHelper
-
var result = db.users.SelectFirst<Table, string>("Name");
This is completely untested and I'm not sure its a thing, but could you create a templated extension method?
public static IQueryable<T> Select(this IQueryable<T> list, string field)
{
// Create an Expression and find the property field
ParameterExpression pe = Expression.Parameter(typeof(string), field);
// Apply the Expression to the IQueryable
}
Here's a link with some expression creation:
https://msdn.microsoft.com/en-us/library/Bb882637.aspx
Have a look at the dynamic linq library:
https://www.nuget.org/packages/System.Linq.Dynamic.Library/
I have a project where I return a list of distinct values from a specified column as follows:
public static List<string> GetValuesFromDB(string column)
{
GradInfoEntities db = new GradInfoEntities();
//if i pass the column name in it doesn't work
//i suspect that if i pass as param, it gets quoted coz i'm passing in the value, not the column
var temp = db.Grads.Where(string.Format("{0} != null", column)).Select(column);
List<string> result = temp.Cast<string>().ToList();
return result;
}
The Dyamanic Linq Library adds overloads for where and select (and a few others, but I've only used those two) that allow you to specify which columns you want to select via a passed in variable.
I've snipped out the sorting and distinct call, but basically I'm returning a string list of all values that aren't null from the passed in column
Edit: I've just checked and there is no First(), but there is a Take(), and an Any()

LINQ to Entities does not recognize the method 'Boolean Contains[Int32]

I have the following extension methods in which I am using to do a Contains on LINQ-To-Entities:
public static class Extensions
{
public static IQueryable<TEntity> WhereIn<TEntity, TValue>
(
this ObjectQuery<TEntity> query,
Expression<Func<TEntity, TValue>> selector,
IEnumerable<TValue> collection
)
{
if (selector == null) throw new ArgumentNullException("selector");
if (collection == null) throw new ArgumentNullException("collection");
if (!collection.Any())
return query.Where(t => false);
ParameterExpression p = selector.Parameters.Single();
IEnumerable<Expression> equals = collection.Select(value =>
(Expression)Expression.Equal(selector.Body,
Expression.Constant(value, typeof(TValue))));
Expression body = equals.Aggregate((accumulate, equal) =>
Expression.Or(accumulate, equal));
return query.Where(Expression.Lambda<Func<TEntity, bool>>(body, p));
}
//Optional - to allow static collection:
public static IQueryable<TEntity> WhereIn<TEntity, TValue>
(
this ObjectQuery<TEntity> query,
Expression<Func<TEntity, TValue>> selector,
params TValue[] collection
)
{
return WhereIn(query, selector, (IEnumerable<TValue>)collection);
}
}
When I call the extenion method to check if a list of ids is in a particular table, it works and I get back the List of ids, like this:
List<int> Ids = _context.Persons
.WhereIn(x => x.PersonId, PersonIds)
.Select(x => x.HeaderId).ToList();
When I execute the next statement, it complains that LINQ-To-Entities does not recogonize Contains(int32), but I thought I am not going against the entity anymore, but a collection of ints.
predicate = predicate.And(x=> Ids.Contains(x.HeaderId));
If I have a comma separated string such as "1,2,3", then the following works:
predicate = predicate.And(x=>x.Ids.Contains(x.HeaderId));
I am trying to take the List returned and create comma separated list of strings, the problem here is that now when I do predicate = predicate.And(x=>sb.Contains(x.HeaderId.ToString());, it complains that it does not like ToString().
I also tried doing:
predicate = predicate.And(x=>Extensions.WhereIn(Ids, x.id));, but it can't resolve WhereIn. It says I must add `<>`, but I am not sure what to add here and how implement it.
Where is nothing wrong with your WhereIn, and you are correct: when you use Ids, you are not going against the entity anymore, but a collection of ints.
Problem is when you're using .And on predicate: LINQ-To-Entities tries to convert everything inside those brackets into Entities methods, and there is no corresponding Contains method.
Solution:
Instead of
predicate = predicate.And(x=> Ids.Contains(x.HeaderId));
use
predicate = predicate.And(Contains<XClassName, int>(x.HeaderId));
where Contains defined as follows:
private static Expression<Func<TElement, bool>> Contains<TElement, TValue>(Expression<Func<TElement, TValue>> valueSelector, List<TValue> values)
{
if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); }
if (null == values) { throw new ArgumentNullException("values"); }
if (!values.Any())
return e => false;
var equals = values.Select(value => (Expression)Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof(TValue))));
return Expression.Lambda<Func<TElement, bool>>(#equals.Aggregate(Expression.Or), valueSelector.Parameters.Single());
}
and XClassName is the name of the class of your x
You cant use array like that, you need to previsit this lambda in order to expand it to primitives. Alternatively you can change underlying provider so it knows how to generate IN statement , as it doesnt by default.
Didnt find post where one guys actually implement it, will updated once I did.
Basically when you use your extension method it is like
x=>arr.Contains(x)
So if you try to execute such lambda agains your entityset etc it will throw you exception saying that parameters can only be primitives.
The reason is that underlying provider doesnt know how to convert .Contains method for array as function parameter into sql query. And in order to solve that you have two options
teach it how to use T[] as parameter and use Contains with this parameter
update your extension method in order to generate new lamda which will use 'allowed' building blocks, ie expressions using primitive types like int, string, guid etc.
Check this article
http://msdn.microsoft.com/en-us/library/bb882521(v=vs.90).aspx
Replace your:
List<int> Ids = _context.Persons
.WhereIn(x => x.PersonId, PersonIds)
.Select(x => x.HeaderId).ToList();
with
var Ids = _context.Persons
.WhereIn(x => x.PersonId, PersonIds)
.Select(x => x.HeaderId).ToList();
and then try.

Building a Linq Query from a List<string> using dynamic linq

I am using dynamic linq to preocess some user requests. The way it was set up is that I would gather the data into Var data
var data = Project.Model.Adhoc.GetData().AsQueryable();
This is basically a select * from a view
Then from there I would loop through all of the options that I have to filter that the user selected
foreach (Filters filter in filters.OrderBy(x=>x.strOrderNumber))
{
along with some checks and permutations, I get down to this
data = data.Where(filter.strFilter + FormatOperator(filter.strOperator) + "#0", filter.strValue).
Select(x => x).ToList().AsQueryable();
This is working pretty well, however the datasource is starting to grow, so what I would like to do is something like this:
data = data.select(get all items that were selected) and then do my checks and permutations. This would allow me to only pull what is needed, not the entire datasource. What is the best way in C# using linq to accomplish this.
Ex.
datasource = {Name, Age, Race, Gender, Hair Color, Eye Color, height, weight, etc}
user selected = {Name, Age, Race, Gender}
Instead of querying against that whole datasource, I want to limit the datasource to only what is brought in by the user off the bat, and then I can filter based on that as teh datasource
Take a look at Dynamic Linq
You can use the DynamicQuery library against any LINQ data provider
(including LINQ to SQL, LINQ to Objects, LINQ to XML, LINQ to
Entities, LINQ to SharePoint, LINQ to TerraServer, etc). Instead of
using language operators or type-safe lambda extension methods to
construct your LINQ queries, the dynamic query library provides you
with string based extension methods that you can pass any string
expression into.
Remove the .ToList() call in the foreach loop.
data = data.Where() will build a query expression with ANDs. So after the loop you can finally invoke .ToList() to finally hit the database.
Update
And the .Select() isn't necessary.
data = data.Where(filter.strFilter + FormatOperator(filter.strOperator) + "#0", filter.strValue);
Update2
Oh, after reading your question again I get that you need to build the query using OR.
This is a little bit more difficult using the standard library. If you don't mind pulling in a extra dependency then it can (probably) be done using LinqKit
IQueryable<Product> SearchProducts (params string[] keywords)
{
var predicate = PredicateBuilder.False<Product>();
foreach (string keyword in keywords)
{
string temp = keyword;
predicate = predicate.Or (p => p.Description.Contains (temp));
}
return dataContext.Products.Where (predicate);
}
Although I am not sure how well that works together with Dynamic Linq.
Otherwise you'll have to handcraft the expression, which might end up looking similar to this:
public static class IQueryableExtensions
{
public static IQueryable<T> WhereIn<T, TValue>(
this IQueryable<T> source,
Expression<Func<T, TValue>> propertySelector,
IEnumerable<TValue> values)
{
return source.Where(GetWhereInExpression(propertySelector, values));
}
private static Expression<Func<T, bool>> GetWhereInExpression<T, TValue>(
Expression<Func<T, TValue>> propertySelector, IEnumerable<TValue> values)
{
if (!values.Any())
return c => false;
ParameterExpression p = propertySelector.Parameters.Single();
// You'll have to adjust this:
var equals = values.Select(value => (Expression)Expression.Equal(
propertySelector.Body, Expression.Constant(value, typeof(TValue))));
var body = equals.Aggregate<Expression>(
(accumulate, equal) => Expression.Or(accumulate, equal));
return Expression.Lambda<Func<T, bool>>(body, p);
}
}

ascending/descending in LINQ - can one change the order via parameter?

I have a method which is given the parameter "bool sortAscending". Now I want to use LINQ to create sorted list depending on this parameter. I got then this:
var ascendingQuery = from data in dataList
orderby data.Property ascending
select data;
var descendingQuery = from data in dataList
orderby data.Property descending
select data;
As you can see, both queries differ only in "ascending" resp. "descending". I'd like to merge both queries, but I don't know how. Does anyone have the answer?
You can easily create your own extension method on IEnumerable or IQueryable:
public static IOrderedEnumerable<TSource> OrderByWithDirection<TSource,TKey>
(this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
bool descending)
{
return descending ? source.OrderByDescending(keySelector)
: source.OrderBy(keySelector);
}
public static IOrderedQueryable<TSource> OrderByWithDirection<TSource,TKey>
(this IQueryable<TSource> source,
Expression<Func<TSource, TKey>> keySelector,
bool descending)
{
return descending ? source.OrderByDescending(keySelector)
: source.OrderBy(keySelector);
}
Yes, you lose the ability to use a query expression here - but frankly I don't think you're actually benefiting from a query expression anyway in this case. Query expressions are great for complex things, but if you're only doing a single operation it's simpler to just put that one operation:
var query = dataList.OrderByWithDirection(x => x.Property, direction);
In terms of how this is implemented, this changes the method - from OrderBy/ThenBy to OrderByDescending/ThenByDescending. However, you can apply the sort separately to the main query...
var qry = from .... // or just dataList.AsEnumerable()/AsQueryable()
if(sortAscending) {
qry = qry.OrderBy(x=>x.Property);
} else {
qry = qry.OrderByDescending(x=>x.Property);
}
Any use? You can create the entire "order" dynamically, but it is more involved...
Another trick (mainly appropriate to LINQ-to-Objects) is to use a multiplier, of -1/1. This is only really useful for numeric data, but is a cheeky way of achieving the same outcome.
What about ordering desc by the desired property,
blah = blah.OrderByDescending(x => x.Property);
And then doing something like
if (!descending)
{
blah = blah.Reverse()
}
else
{
// Already sorted desc ;)
}
Is it Reverse() too slow?
In addition to the beautiful solution given by #Jon Skeet, I also needed ThenBy and ThenByDescending, so I am adding it based on his solution:
public static IOrderedEnumerable<TSource> ThenByWithDirection<TSource, TKey>(
this IOrderedEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
bool descending)
{
return descending ?
source.ThenByDescending(keySelector) :
source.ThenBy(keySelector);
}

Categories

Resources