Use LINQ for arbitrary sorting - linq

Let's say we have an entity that has attributes att1 and att2, where att1 can have values a,b,c and att2 can have values 1,2,3. Is it possible to use LINQ so that we can sort items in the collection by applying arbitrary sorting rule without implementing IComparable. I am facing an issue were business requires that on some screens items in the collection be sorted one way and in other screens some other way. For example rule can state that items need to be sorted so that "b" is listed first, then "a", then "c" and within each group, "3" is first, then "1" then "2".

Sure. You can use OrderBy with a predicate that returns more or less whatever kind of arbitrary "sort order" you please. For example:
objectsWithAttributes.OrderBy(x =>
{
// implement your "rules" here -- anything goes as long as you return
// something that implements IComparable in the end. this code will sort
// the enumerable in the order 'a', 'c', 'b'
if (x.Attribute== 'a')
return 0;
else if (x.Attribute== 'c')
return 1;
else if (x.Attribute== 'b')
return 2;
}).ThenBy(x =>
{
// implement another rule here?
});

Related

Iterate over table in order of value

Lets say I have a table like so:
{
value = 4
},
{
value = 3
},
{
value = 1
},
{
value = 2
}
and I want to iterate over this and print the value in order so the output is like so:
1
2
3
4
How do I do this, I understand how to use ipairs and pairs, and table.sort, but that only works if using table.insert and the key is valid, I need to be loop over this in order of the value.
I tried a custom function but it simply printed them in the incorrect order.
I have tried:
Creating an index and looping that
Sorting the table (throws error: attempt to perform __lt on table and table)
And a combination of sorts, indexes and other tables that not only didn't work, but also made it very complicated.
I am well and truly stumped.
Sorting the table
This was the right solution.
(throws error: attempt to perform __lt on table and table)
Sounds like you tried to use a < b.
For Lua to be able to sort values, it has to know how to compare them. It knows how to compare numbers and strings, but by default it has idea how to compare two tables. Consider this:
local people = {
{ name = 'fred', age = 43 },
{ name = 'ted', age = 31 },
{ name = 'ned', age = 12 },
}
If I call sort on people, how can Lua know what I intend? I doesn't know what 'age' or 'name' means or which I'd want to use for comparison. I have to tell it.
It's possible to add a metatable to a table which tells Lua what the < operator means for a table, but you can also supply sort with a callback function that tells it how to compare two objects.
You supply sort with a function that receives two values and you return whether the first is "less than" the second, using your knowledge of the tables. In the case of your tables:
table.sort(t, function(a,b) return a.value < b.value end)
for i,entry in ipairs(t) do
print(i,entry.value)
end
If you want to leave the original table unchanged, you could create a custom 'sort by value' iterator like this:
local function valueSort(a,b)
return a.value < b.value;
end
function sortByValue( tbl ) -- use as iterator
-- build new table to sort
local sorted = {};
for i,v in ipairs( tbl ) do sorted[i] = v end;
-- sort new table
table.sort( sorted, valueSort );
-- return iterator
return ipairs( sorted );
end
When sortByValue() is called, it clones tbl to a new sorted table, and then sorts the sorted table. It then hands the sorted table over to ipairs(), and ipairs outputs the iterator to be used by the for loop.
To use:
for i,v in sortByValue( myTable ) do
print(v)
end
While this ensures your original table remains unaltered, it has the downside that each time you do an iteration the iterator has to clone myTable to make a new sorted table, and then table.sort that sorted table.
If performance is vital, you can greatly speed things up by 'caching' the work done by the sortByValue() iterator. Updated code:
local resort, sorted = true;
local function valueSort(a,b)
return a.value < b.value;
end
function sortByValue( tbl ) -- use as iterator
if not sorted then -- rebuild sorted table
sorted = {};
for i,v in ipairs( tbl ) do sorted[i] = v end;
resort = true;
end
if resort then -- sort the 'sorted' table
table.sort( sorted, valueSort );
resort = false;
end
-- return iterator
return ipairs( sorted );
end
Each time you add or remove an element to/from myTable set sorted = nil. This lets the iterator know it needs to rebuild the sorted table (and also re-sort it).
Each time you update a value property within one of the nested tables, set resort = true. This lets the iterator know it has to do a table.sort.
Now, when you use the iterator, it will try and re-use the previous sorted results from the cached sorted table.
If it can't find the sorted table (eg. on first use of the iterator, or because you set sorted = nil to force a rebuild) it will rebuild it. If it sees it needs to resort (eg. on first use, or if the sorted table was rebuilt, or if you set resort = true) then it will resort the sorted table.

Performing linq search using substring - is there a better way?

I'm using Entity Framework and have started writing queries using linq.
I have a Store table where each store has a name field. One of the searches I want to do is by initial letter and I'm trying to find the best way of achieving it. By best I mean most efficient.
Most searches only look for one key, 'A', 'B', 'C' etc but one of the searches is different in that the group '0-9' will contain a list of keys, one for 0, one for 1 etc. So my starting point is a list of some kind.
I then need to say if a Store name starts with any key in the list, because a Store does not store the initial letter in the table.
There are 2 things I'm looking for help with. Firstly how to get the linq working as I've outlined. Secondly, any advice as to whether there this is the best/only approach to bringing back the data or whether there may be a better way.
Your question is not very clear, but from what I understand you want to search for stores which begin with a key found in a list of keys. You can achieve that like this:
List<string> keys = new List<string>() { "A", "B", "M" };
var result = stores.Where(store => keys.Any(key => store.Name.StartsWith(key));
If the queries can differ, then for the letters:
stores.Where(s=>s.Name.First == c);
And for numerals:
stores.Where(char.IsDigit);
If they must be the same, then I suggest a character range:
stores.Where(s=> c1 <= s.Name.First && s.Name.First <= c2)
You can represent the ranges by Tuples if you want:
Tuple<char, char> range = Tuple.Create('A', 'A');
//Tuple<char, char> range = Tuple.Create('0', '9');
stores.Where(s=> range.Item1 <= s.Name.First && s.Name.First <= range.Item2)
EDIT: Using the function in the Entities Framework
Tuple<char, char> range = Tuple.Create('A', 'A');
//Tuple<char, char> range = Tuple.Create('0', '9');
stores.Where(s=> range.Item1 <= s.Name.Substring(0, 1) && s.Name.Substring(0, 1) <= range.Item2)
I asked a more specific question on another thread.
Here is the answer:
Linq to entities - first letter of string between 2 keys

Reversing IQueryable based on passed property for sorting logic

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();

EF - Linq Expression and using a List of Ints to get best performance

So I have a list(table) of about 100k items and I want to retrieve all values that match a given list.
I have something like this.
the Table Sections key is NOT a primary key, so I'm expecting each value in listOfKeys to return a few rows.
List<int> listOfKeys = new List<int>(){1,3,44};
var allSections = Sections.Where(s => listOfKeys.Contains(s.id));
I don't know if it makes a difference but generally listOfKeys will only have between 1 to 3 items.
I'm using the Entity Framework.
So my question is, is this the best / fastest way to include a list in a linq expression?
I'm assuming that it isn't better to use another .NETICollection data object. Should I be using a Union or something?
Thanks
Suppose the listOfKeys will contain only small about of items and it's local list (not from database), like <50, then it's OK. The query generated will be basically WHERE id in (...) or WHERE id = ... OR id = ... ... and that's OK for database engine to handle it.
A Join would probably be more efficient:
var allSections =
from s in Sections
join k in listOfKeys on s.id equals k
select s;
Or, if you prefer the extension method syntax:
var allSections = Sections.Join(listOfKeys, s => s.id, k => k, (s, k) => s);

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