Reversing IQueryable based on passed property for sorting logic - linq

I am implementing sort based on parameter passed to ascending or descending OrderBy method
else if (showGrid.Sortdir == "DESC")
{
alerts = DB.Incidents.OfType<Alert>().Where(
a =>
a.IncidentStatusID == (int)AlertStatusType.New ||
a.IncidentStatusID == (int)AlertStatusType.Assigned ||
a.IncidentStatusID == (int)AlertStatusType.Watching)
.OrderByDescending(a => showGrid.Sort);
}
else
{
alerts = DB.Incidents.OfType<Alert>().Where(
a =>
a.IncidentStatusID == (int)AlertStatusType.New ||
a.IncidentStatusID == (int)AlertStatusType.Assigned ||
a.IncidentStatusID == (int)AlertStatusType.Watching)
.OrderBy(a => showGrid.Sort);
}
In case of ascending order sorting it works fine but for descending order sorting doesn't work. I debugged the code and I found that list is not revered its same as ascending order. Please help me

Ok. I've written a small test. It is funny, but your code can actually compile and work, but very differently from what you expect :)
Obviously showGrid is not of type Alert, it is an instance of some other class, that incidentally have the same propery as Alert, called Sort.
First I was confused, because expected this code to fail to compile.
// The signature of OrderBy
public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector)
// In your case it will result in
public static IOrderedQueryable<Alert> OrderBy<Alert, string>(this IQueryable<Alert> source, Expression<Func<Alert, string>> keySelector)
//when you call it like you do
DB.Incidents.OfType<Alert>().OrderByDescending(a => showGrid.Sort);
// You supply a property from object of type different from your entity.
// This is incorrect usage, the only object you can use here is the
// "a" argument. Like this:
DB.Incidents.OfType<Alert>().OrderByDescending(a => a.Sort);
// Because anything else does not make any sense to entity provider.
So your order by simply does not work.
As far as I understood, what you want is to perform sorting based on selection in UI. This is not easily achieved in strongly-typed LINQ. Because as I showed above, you send a property, not a value to the OrderBy. It does not care about the value inside the prop. So there are several solutions to the problem:
Write a big switch, that will check every possible Sort value, and will append appropriate 'OrderBy(a => a.YouPropToSort)' to the query. This is straitforward, and you should begin with this. Of course this is a static way, and will require to change code everytime you want new columns to be added for sorting.
Create argument for your OrderBy using 'LINQ Expression Trees'. For you case it should not be very hard to do. Look for the term, you will find a lot of examples.
Try to use Dynamic LINQ. I did not not use it myself, just looked at the docs. This seems to be an extension to the normal LINQ which allows you to write parts of queries as strings, to overcome limitations like the current one with dynamic sorting.

Here's my solution to sorting based on user selections:
Create your base query
var query = DB.Incidents.OfType<Alert>.Where(
a =>
a.IncidentStatusID == (int)AlertStatusType.New ||
a.IncidentStatusID == (int)AlertStatusType.Assigned ||
a.IncidentStatusID == (int)AlertStatusType.Watching);
and then apply your sort using a case statement
bool desc = showGrid.SortDir = "DESC";
switch(showGrid.Sort)
{
case "col1":
query = desc ? query.OrderByDescending( a => a.Col1 ) : query.OrderBy( a => a.Col1 );
break;
case "col2":
query = desc ? query.OrderByDescending( a => a.Col2 ) : query.OrderBy( a => a.Col2 );
break;
...
}
var results = query.ToList();

Related

Is Select optional in a LINQ statement?

I was looking over some LINQ examples, and was thereby reminded they are supposed to have a "select" clause at the end.
But I have a LINQ that's working and has no "Select":
public IEnumerable<InventoryItem> Get(string ID, int packSize, int CountToFetch)
{
return inventoryItems
.Where(i => (i.Id.CompareTo(ID) == 0 && i.PackSize > packSize) || i.Id.CompareTo(ID) > 0)
.OrderBy(i => i.Id)
.ThenBy(i => i.PackSize)
.Take(CountToFetch)
.ToList();
}
Is this because:
(a) select is not really necessary?
(b) Take() is doing the "select"
(c) ToList() is doing the "select"
Truth be told, this was working before I added the "ToList()" also... so it seems LINQ is quite permissive/lax in what it allows one to get away with.
Also, in the LINQ I'm using, I think the OrderBy and ThenBy are redundant, because the SQL query used to populate inventoryItems already has an ORDER BY ID, PackSize clause. Am I right (that the .OrderBy() and .ThenBy() are unnecessary)?
Linq statements do in fact need a select clause (or other clauses, such as a group by). However, you're not using Linq syntax, you're using the Linq Enumerable extension methods, which all (for the most part) return IEnumerable<T>. Therefore, they do not need the Select operator.
var result = from item in source
where item.Value > 5
select item;
Is exactly the same as
var result = source.Where(item => item.Value > 5);
And for completeness:
var result = from item in source
where item.Value > 5
select item.Value;
Is exactly the same as
var result = source.Where(item => item.Value > 5)
.Select(item => item.Value);
Linq statements (Linq syntax statements) need a special clause at the end to signify what the result of the query should be. Without a select, group by, or other selection clause, the syntax is incomplete, and the compiler does not know how to change the expression into the appropriate extension methods (which is what Linq syntax actually gets compiled to).
As far as ToList goes, that's one of the Enumerable extension methods that does not return an IEnumerable<t>, but instead a List<T>. When you use ToList or ToArray the Enumerable is enumerated immediately and converted to a list or array. This is useful if your query is complex and you need to enumerate the results multiple times without running the query multiple times).
You only use select when you want to project your object into a different type..
if you had a list that contains an object with an ID property that was an int
var newList = items.Select(i => i.ID);
newList would be an IEnumerable<int>
NB.
A common mistake is to mix up a Select with a Where.
items.Where(i => i.ID == 1); returns an IEnumerable<item>
items.Select(i => i.ID == 1); returns an IEnumerable<bool>
as the Select projects each item into the result of the function passed in..

Can linq predicates be combined here?

I am using EF4.3 with a unit-of-work + repository pattern.
I have a method which act as a base of some other methods, here's how the code looks.
This is my 'base' method:
public static IQueryable<Deal> FindActive()
{
var r = new ReadRepo<Deal>(Local.Items.Uow.Context);
return r.Find(d =>
d.ActiveFrom <= DateTime.Now &&
(d.ActiveUntilComputed == null || d.ActiveUntilComputed > DateTime.Now) &&
d.Published);
}
Here is one of the methods that calls the base method:
public static IQueryable<Deal> FindActiveByStore(int storeId)
{
Guard.Default(storeId, "storeId");
return FindActive().Where(d => d.StoreId == storeId);
}
As you can see in FindActiveByStore, I first call FindActive, which then chains Find(). FindActive is followed by a Where() to add a secondary predicate (excuse the terminology).
I wondered if it was possible to pass a predicate to FindActive instead of using a Where(), and in fact whether it would make a difference in terms of performance.
Like this:
FindActive(d => d.StoreId == storeId)
FindActive already passes a predicate to Find() so it would need to combine both.
I'm guessing that the answers I get back will be along the lines 'its not worth it' in terms of effort or performance but I thought i'd ask the experts anyway.
You can use this code (You reduce number lines of code)
public IQueryable<Deal> FindActiveByStore(Expression<Func<Deal,bool>> predicate)
{
var r = new ReadRepo<Deal>(Local.Items.Uow.Context);
return r.Find(d => d.ActiveFrom <= DateTime.Now
&& (d.ActiveUntilComputed == null || d.ActiveUntilComputed > DateTime.Now)
&& d.Published)
.Where(predicate);
}

Is there a way to improve this LINQ?

Code :
IList<Evento> Eventi = new List<Evento>() { };
Eventi = (from Evento ae in new Eventi()
select ae).ToList();
if (strNome != "")
{
Eventi = Eventi.Where(e => e.Titolo.ToLower().Contains(strNome.ToLower()) && e.Titolo != "").ToList();
}
if (strComune != "")
{
Eventi = Eventi.Where(e => e.Comune != null && e.IDComune == strComune).ToList();
}
if (strMesi != "")
{
Eventi = Eventi.Where(e => MesiSelezionati.Contains(DateTime.Parse(e.DataEvento).Month.ToString())).ToList();
}
I know all query are merged, during running time of code, in only 1 LINQ statement. But, as you can see, I convert the List -> ToList() many times. This, I think, here, is the only part when I waste time, right? How can I avoid this and improve performance?
Why so many lists/ToLists? What's wrong with IEnumerable/IQueryable?
var eventi = (from Evento ae in new Eventi()
select ae);
if (strNome != "")
{
eventi = eventi.Where(e => e.Titolo.ToLower().Contains(strNome.ToLower()) && e.Titolo != "");
}
if (strComune != "")
{
eventi = eventi.Where(e => e.Comune != null && e.IDComune == strComune);
}
if (strMesi != "")
{
eventi = eventi.Where(e => MesiSelezionati.Contains(DateTime.Parse(e.DataEvento).Month.ToString()));
}
// if you do need a list, then do so right at the end
var results = eventi.ToList();
EDIT: To clarify some principles for Caesay
Caesay, thank you for taking the time to test the implementation to confirm the deferred loading works as intended; much kudos!
I wanted to explain why I disagree with your comment about the above approach being optimized at run-time whilst yours being optimized at compile time.
The above approach is, for lack of a better description, the intended approach. This is because the assignments to eventi are correctly appending expressions to the source of the IEnumerable/IQueryable.
Your approach is only supported by certain providers, such as Linq to Entities, which expect a Func(Of T, TResult) to be passed to their Select, Where, etc Extensions. Many providers, such as Entity Framework and Linq to Sql provider, provide IQueryable, which implements IEnumerable. The difference here, however, is that IQueryable's Select, Where, etc, expect you to pass an Expression(Of Func(Of T, TResult)).
In those cases, your code will not behaveas expected (or at least as I would expect), because Expression does not support multi-line lambda, where as the compiler will correctly interpret my statements and compile them into Expression>.
As a simple example:
public void Test<T1, T2>(System.Linq.Expressions.Expression<Func<T1, T2>> arg)
{
throw new NotImplementedException();
}
public void Test()
{
Test((string x) => x.ToLower());
Test((string x) =>
{
return x.ToLower();
});
}
In the above example, the first expression is absolutely fine. The second, which is based loosely on your example, will fail with the exception:
A lambda expression with a statement body cannot be converted to an expression tree
The compiler may recognise your statements as a Func which it knows is supported on IEnumerable. The result would be that the query to the database would not include any of your Where expressions, returning the whole data source. Once the data source was in-memory, it would then apply your IEnumerable Where clauses. Personally, I much prefer passing these kind of things to the database so that I'm not wasting bandwidth on returning much more data than I need, and I can utilise my Data Sources ability to filter data which is likely (and vastly in Sql Server's case) better than doing so in-memory.
I hope that makes sense and is of some use to you?
You can use fluent syntax to start with (to avoid List).
Alternatively you can combine the condition in one query.
Eventi =(new Eventi()).Where(e => e.Titolo.ToLower().Contains(strNome.ToLower()) && e.Titolo != "" && e.Comune != null && e.IDComune == strComune &&MesiSelezionati.Contains(DateTime.Parse(e.DataEvento).Month.ToString())).ToList();
I have assumed Eventi implements IEnumerable < Evento > as you have used similarly in query syntax
Given that you test for strNome not being empty, the second half of your first Where clause will never be called.
So e.Titolo.ToLower().Contains(strNome.ToLower()) && e.Titolo != ""
can be written e.Titolo.ToLower().Contains(strNome.ToLower())
You could also calculate strNome.ToLower() outside the lambda to ensure it is calculated just once.
In addition you can streamline the Where clauses and remove the ToList() as others have suggested.
Another alternative you should be aware of is LinqKit which allows you to combine lambda expressions more easily which although not needed in this case since Where ... Where is good enough for an 'And', you might one day need an 'Or' and then you need a different solution.
Or, better yet, use the method explained here to create your own And and Or methods that perform Expression 'magic' to give you a single expression that you can hand off to Linq-to-Sql or any other Linq provider.
Combine the whole thing into 1 linq query like this:
var eventi = from Evento e in new Eventi() select e;
eventi = eventi.Where(e =>
{
if (strNome != "")
{
if(!(e.Titolo.ToLower().Contains(strNome.ToLower()) && e.Titolo != ""))
return false;
}
if (strComune != "")
{
if(!(e.Comune != null && e.IDComune == strComune))
return false;
}
if (strMesi != "")
{
if(!(MesiSelezionati.Contains(DateTime.Parse(e.DataEvento).Month.ToString())))
return false;
}
return true;
});
var results = eventi.ToList();
This is logically equivalent to your code, but should be much faster. Although I was not able to test it, because I can't compile it.

Calling a function within a Linq query

If I want to iterate through a collection, and call a function on each element in the collection, I could go with :
foreach (var obj in objColl)
{
MyFunction(obj);
}
Should I want to do this with linq, I can use either of those :
//#1
var unused = (from var obj in objColl select MyFunction(obj)).ToList();
//#2
var unused = objColl.Select(obj => MyFunction(obj)).ToList();
I know this works, but it doesn't seem right. Of course, my actual cases are more complex queries that that, but it comes down to this since I could build my IQueryable with Linq and iterate through it and call the function.
Edit:
Here is one example of what I did. (Item# are things I can't disclose)
var dummyValue = (from
Item7 in dal.GetAgencyConvertions().Where(age => age.SourceName == "Item1" && age.TargetName == "Item2")
join Item6 in dal.GetAgencyConvertions().Where(age => age.SourceName == "Item2" && age.TargetName == "Item3") on Item6.TargetValue equals Item7.SourceValue
join agency in dal.GetAgencies() on Item7.SourceValue equals agency.Agency
orderby Item7.TargetValue
select vl.ValueListItems.Add(agency.ID, Item7.TargetValue)).ToList();
Go with the simple foreach, as you are clearly wanting to perform an action on (and/or using) the objects in your collection as opposed to wishing to project/filter/group/etc. the items in the sequence. LINQ is about the latter set of operations.
Edit: In the case of your update, I would simply create a query, and then iterate over the query in the foreach to perform the action.
var query = from Item7 in dal.GetAgencyConvertions().Where(age => age.SourceName == "Item1" && age.TargetName == "Item2")
join Item6 in dal.GetAgencyConvertions().Where(age => age.SourceName == "Item2" && age.TargetName == "Item3") on Item6.TargetValue equals Item7.SourceValue
join agency in dal.GetAgencies() on Item7.SourceValue equals agency.Agency
orderby Item7.TargetValue
select new { ID = agency.ID, Value = Item7.TargetValue };
foreach (var item in query)
vl.ValueListItems.Add(item.ID, item.Value);
To be frank, you have the same loop happening in your code, you merely mask it by using the ToList() extension method. As a byproduct, you are creating a list of values that you have no intention of using, while somewhat obfuscating the true intention of the code, all to save maybe a few characters.
Typically, a query shouldn't have any side effects (i.e. it shouldn't modify the state of the data or other data in your application) which raises the question, does MyFunction modify the state of your application? If it does, then you should use a foreach loop.
How about an Each() extension method?
public static void Each<T>(this IEnumerable<T> target, Action<T> action)
{
if (target == null) return;
foreach (T obj in target)
action(obj);
}

LINQ OrderBy versus ThenBy

Can anyone explain what the difference is between:
tmp = invoices.InvoiceCollection
.OrderBy(sort1 => sort1.InvoiceOwner.LastName)
.OrderBy(sort2 => sort2.InvoiceOwner.FirstName)
.OrderBy(sort3 => sort3.InvoiceID);
and
tmp = invoices.InvoiceCollection
.OrderBy(sort1 => sort1.InvoiceOwner.LastName)
.ThenBy(sort2 => sort2.InvoiceOwner.FirstName)
.ThenBy(sort3 => sort3.InvoiceID);
Which is the correct approach if I wish to order by 3 items of data?
You should definitely use ThenBy rather than multiple OrderBy calls.
I would suggest this:
tmp = invoices.InvoiceCollection
.OrderBy(o => o.InvoiceOwner.LastName)
.ThenBy(o => o.InvoiceOwner.FirstName)
.ThenBy(o => o.InvoiceID);
Note how you can use the same name each time. This is also equivalent to:
tmp = from o in invoices.InvoiceCollection
orderby o.InvoiceOwner.LastName,
o.InvoiceOwner.FirstName,
o.InvoiceID
select o;
If you call OrderBy multiple times, it will effectively reorder the sequence completely three times... so the final call will effectively be the dominant one. You can (in LINQ to Objects) write
foo.OrderBy(x).OrderBy(y).OrderBy(z)
which would be equivalent to
foo.OrderBy(z).ThenBy(y).ThenBy(x)
as the sort order is stable, but you absolutely shouldn't:
It's hard to read
It doesn't perform well (because it reorders the whole sequence)
It may well not work in other providers (e.g. LINQ to SQL)
It's basically not how OrderBy was designed to be used.
The point of OrderBy is to provide the "most important" ordering projection; then use ThenBy (repeatedly) to specify secondary, tertiary etc ordering projections.
Effectively, think of it this way: OrderBy(...).ThenBy(...).ThenBy(...) allows you to build a single composite comparison for any two objects, and then sort the sequence once using that composite comparison. That's almost certainly what you want.
I found this distinction annoying in trying to build queries in a generic manner, so I made a little helper to produce OrderBy/ThenBy in the proper order, for as many sorts as you like.
public class EFSortHelper
{
public static EFSortHelper<TModel> Create<TModel>(IQueryable<T> query)
{
return new EFSortHelper<TModel>(query);
}
}
public class EFSortHelper<TModel> : EFSortHelper
{
protected IQueryable<TModel> unsorted;
protected IOrderedQueryable<TModel> sorted;
public EFSortHelper(IQueryable<TModel> unsorted)
{
this.unsorted = unsorted;
}
public void SortBy<TCol>(Expression<Func<TModel, TCol>> sort, bool isDesc = false)
{
if (sorted == null)
{
sorted = isDesc ? unsorted.OrderByDescending(sort) : unsorted.OrderBy(sort);
unsorted = null;
}
else
{
sorted = isDesc ? sorted.ThenByDescending(sort) : sorted.ThenBy(sort)
}
}
public IOrderedQueryable<TModel> Sorted
{
get
{
return sorted;
}
}
}
There are a lot of ways you might use this depending on your use case, but if you were for example passed a list of sort columns and directions as strings and bools, you could loop over them and use them in a switch like:
var query = db.People.AsNoTracking();
var sortHelper = EFSortHelper.Create(query);
foreach(var sort in sorts)
{
switch(sort.ColumnName)
{
case "Id":
sortHelper.SortBy(p => p.Id, sort.IsDesc);
break;
case "Name":
sortHelper.SortBy(p => p.Name, sort.IsDesc);
break;
// etc
}
}
var sortedQuery = sortHelper.Sorted;
The result in sortedQuery is sorted in the desired order, instead of resorting over and over as the other answer here cautions.
if you want to sort more than one field then go for ThenBy:
like this
list.OrderBy(personLast => person.LastName)
.ThenBy(personFirst => person.FirstName)
Yes, you should never use multiple OrderBy if you are playing with multiple keys.
ThenBy is safer bet since it will perform after OrderBy.

Resources