C# Linq OrderBy based on condition - c#

I have a foreach loop that I want to change:
foreach (var line in lines.OrderBy(x=> x.ColA))
If a condition is met, then instead of ordering by ColA, I want to order by ColB.
I know this could be done like the following:
var orderLines = new List<OrderLines>();
if (condition)
orderLines = lines.OrderBy(x => x.ColB).ToList();
else
orderLines = lines.OrderBy(x => x.ColA).ToList(); ;
foreach (var line in orderLines)
But I am sure there is a more elegant solution.

Several solutions.
(1) Don't do the ToList() before your foreach, only create the IEnumerable.
IEnumerable<OrderLines> orderLines = condition ?
lines.OrderBy(orderLine => orderLine.ColB) :
lines.OrderBy(orderLine => orderLine.ColA);
foreach(OrderLine orderlLine in orderLines) {...}
(2) If you will be using this on several locations, consider to create an extension method. This way your method looks like any other LINQ method.
See extension methods demystified
public static IEnumerable<OrderLine> OrderBy(
this IEnumerable<OrderLine> source,
bool condition)
{
return condition ?
lines.OrderBy(orderLine => orderLine.ColB) :
lines.OrderBy(orderLine => orderLine.ColA);
}
Usage:
If operator checks chexBox1, sort by colB, else sort by colA:
IEnumerable<OrderLine> lines = ...
foreach(var sortedOrderLine in lines.OrderBy(this.CheckBox1.IsChecked))
{
...
}
Because it is an extension method of IEnumerable<OrderLine>, you can even intertwine it with other LINQ methods:
var result = lines.Where(orderLine => orderLine.Date.Year >= 2020)
.OrderBy(this.checkBox1.IsChecked)
.Select(orderLine => new
{
Id = orderLine.Id,
Price = orderLine.Price,
});
But all in all, it doesn't save you a lot of code. The only advantage would be if you would use it in a lot of methods. In that case, a change in how you want to OrderBy condition would have to be changed in only one place. But again: if you expect to use it in one place, moving it to a separate method might not help readers to understand what happens.

That is probably about as good as it gets.
Remember that behind that lambda expression magic happens which (effectively) binds to a Comparer<T> where T depends on the type of the columns being compared.
To make this more terse might make it less efficient. Specifically converting and comparing strings makes it both slower and can get you into trouble (ints sort to 1,2,3,...10,11,... vs their strings to "1","10","11",..."19","2","20","21"...).
A "one-liner" is only elegant if it's behaviour is obvious, otherwise it is obfuscated.
Your code is fine. (IMO;-)

Install NuGet System.Linq.Dynamic and you can pass property name as string to OrderBy like below.
Usage list.AsQueryable().OrderBy("PropertyName1 SortOrder, ropertyName SortOrder"). Where PropertyName will be ColA ColB. And SortOrder will be ASC DESC.
Add using System.Linq.Dynamic;
foreach (var line in lines.AsQueryable().OrderBy(condition ? "ColB" : "ColA")
For .Net Core install NuGet System.Linq.Dynamic.Core.
Add using System.Linq.Dynamic.Core;
foreach (var line in lines.AsQueryable().OrderBy(condition ? "ColB" : "ColA")
For a better practice rather than providing PropertyName as string use nameof(Class.Property) like in your case nameof(OrderLines.ColA). So in case you change ColA property it will show Builderror and you will not get run time exception.
foreach (var line in lines.AsQueryable().OrderBy(condition ? nameof(OrderLines.ColB) : nameof(OrderLines.ColA))

As #AlanK have mentioned, the closest we can get for simplifying OrderBy would be something like:
Func<OrderLines, string> selector = (orderLine) => condition ? orderLine.ColB : orderLine.ColA;
List<OrderLines> orderLines = lines.OrderBy(selector);
provided both ColA and ColB are of same data type. Otherwise it wouldn't be efficient due to the overhead of data type conversion.

Related

C# - check if substring is present in arraylist of strings

Suppose I've an Arraylist(arr1) like the below
"String1 is present"
"String2 is present"
"String3 is present"
i wanted to see if 'String2' is present in this arraylist. i've done something like the below:
var containsstringmatch = arr1.OfType<string>().Any(arg=>arg.Contains("String2"));
if (containsstringmatch==true)
{
IEnumerable v1 = arr1.OfType<string>().Where(arg=>arg.Contains("String2"));
foreach (string s in v1)
{
st1 = s;
}
Console.WriteLine(st1);
}
which gives me the below output which is good:
"String2 is present"
I wanted to see if this can be achieved without me using the foreach loop. Can someone please provide suggestions as to how to do it.
Thanks
If you want only to print the first string that contains the search, you can use FirstOrDefault():
var foundString = arr1.OfType<string>().FirstOrDefault(arg => arg.Contains("String2"));
Console.WriteLine(string.IsNullOrEmpty(foundString) ? "Not found" : foundString);
Also, as Aomine wrote in his answer - ArrayLists where good when we worked with .Net 1.1. Since .Net 2.0 introduced generics, ArrayLists should be avoided.
As Rufus L wrote in his comment, your current code gets the last string containing the search string, not the first. If you want the last and not the first, you can simply use LastOrDefault instead of FirstOrDefault.
I'd avoid using ArrayList in this day and age in .NET, instead, favor the List<T> (if possible).
As for:
I wanted to see if this can be achieved without me using the foreach
loop.
if by this you mean that you want to avoid the foreach construct and perform everything inline:
arr1.OfType<string>()
.Where(arg => arg.Contains("String2"))
.ToList()
.ForEach(s => Console.WriteLine(s));
or if you just want to find the last element satisfying the said criteria:
var result = arr1.OfType<string>().LastOrDefault(arg => arg.Contains("String2"));
There is no way to do this without a foreach or for loop. But you can create an extension method that will move the code out of your method.
public static class ConsoleExtensions
{
public static void WriteToConsole(this IEnumerable<string> list)
{
foreach (string item in list)
Console.WriteLine(item);
}
}
usage:
arr1.OfType<string>().Where(arg=>arg.Contains("String2")).WriteToConsole();

SQLiteNet Index and Lambda expressions

We are using Xamarin with SQLiteNet as ORM.
In our data layer class we have the method below.
filter = ri => ri.ItemVersioniId == itemVersionId;
The method is getting the records matching the Id. If the lambda expression is hardcoded, instead of using the "filter" parameter it is much faster... even though it is the same logic.
We would to be able to pass the filter as a parameter but still get a good performance. Any advise?
public virtual List<ResourceItem> GetResourceItems (string itemVersionId, Func<ResourceItem,bool> filter ){
//var t = db.Table<ResourceItem> ().Where (ri => ri.ItemVersionId == itemVersionId); --* this line is 10 times faster
var t = db.Table<ResourceItem> ().Where (filter); --* this line is 10 times slower
return new List<ResourceItem> (t);
}
I'm not sure because it is xamarin specific, but i suggest to use Expression instead of Func.
Expression<Func<ResourceItem,bool>> filter =
ri => ri.ItemVersioniId == itemVersionId;
public virtual List<ResourceItem> GetResourceItems
(string itemVersionId, Expression<Func<ResourceItem,bool>> filter )
{
return db.Table<ResourceItem> ().Where (filter).ToList();
}
I would advise hard coding it. Here is why, but first let me qualify this by saying I am speculating - I have no experience with SQLiteNet - this based on some general, rudimentary knowledge on how LINQ Prodivers work.
When you have it hard coded the lambda expression is converted to SQL at compile time. When you set it to a delegate, it could be a LINQ to Objects query, there is no way to know at compile time that your LINQ Provider can convert that to a SQL statement. Instead this work occurs at runtime and as a result the performance suffers greatly.

What's the syntax for this linq expression calling the in method

I have to query a repository where column a = {myvalue} and column b has any value in a collection.
Here's what I have:
Application[] applicationCollection = GetAllApplications();
var result = repo.GetAll(r => r.Email == myEmail && r.Application.In(applicationCollection));
I can't figure out how to write the "in" part...
Preferrably links to how to do this would be best, so I can learn it, as opposed to just getting the answer :). Thanks all.
(I'm sure this is a repeat question, but my Google/search skills are obviously poor.)
The SQL idea of item in collection is written in C# (including LINQ) as collection.Contains(item). In your example, this might be:
var result = repo.GetAll(r => r.Email == myEmail &&
applicationCollection.Contains(r.Application));
ApplicationCollection.Contains(r.Application)
Use .Contains(collection) instead of in. Here's a link since you wanted one.
If you want to write it the way you've shown, you can write this extension method:
public static bool In<T>(this T item, IEnumerable<T> set)
{
return set.Contains(item);
}
And then use it exactly like you did in your question:
Application[] applicationCollection = GetAllApplications();
var result = repo.GetAll(r =>
r.Email == myEmail &&
r.Application.In(applicationCollection));
This will work fine if you're only working with in-memory sets. If you're doing something like LINQ-to-SQL, however, this won't be compatible.
Note: I'm not trying to imply that this extension method might be a good idea to use - I'm just saying it's possible to write one like that.

Filtering a string list with logic?

I have a list of strings. I need to be able to filter them in a similar way to a Google query.
Ex: NOT water OR (ice AND "fruit juice")
Meaning return strings that do not have the word water or return strings that can have water if they have ice and "fruit juice".
Is there a mechanism in .NET that can allow the user to write queries in this form (say in a textbox) and given a List or IEnumerable of string, return the ones that contain this.
Can LINQ maybe do something like this?
I am aware that I can do this with LINQ, I'm more concerned with parsing an arbitrary string into an executable expression.
There is nothing built in.
You will need to parse such a string yourself and possibly use the Expression classes to build up an executable expression from which to filter.
For this query: Meaning return strings that do not have the word water or return strings that can have water if they have ice and "fruit juice".
Try something like this if you are going to use LINQ
yourList.Where(i => !i.Contains("water") ||
(i.Contains("water") &&
i.Contains("ice") &&
i.Contains("fruit juice")));
I think we can't answer you using a code sample since as you said this is more logical.
what I would do in such a scenario is to have predefined conditions (saved somewhere) which will contain all the conditions you need along with their "coding translations" like:
and
or
and not
and or
etc...
and what you will do at runtime is to translate these conditions/criteria into sql or linq or whatever language you need to pass it to.
In LINQ: list.Where(item => !item.Contains("water") || (item.Contains("ice") && item.Contains("fruit juice")))
You can try to use the DataTable.Select method:
public static class ExpressionExtensions {
public static IEnumerable<T> Select<T>(this IEnumerable<T> self, string expression) {
var table = new DataTable();
table.Columns.Add("Value", typeof(T));
foreach (var item in self) {
var row = table.NewRow();
row["Value"] = item;
table.Rows.Add(item);
}
return table.Select(expression).Select(row => (T)row["Value"]);
}
}
But you have to follow its format to create your expression:
var filtered = strings.Select("NOT Value LIKE '*water*' OR (Value LIKE '*ice*' AND Value LIKE '*fruit juice*')");
Also note that in this case, since the string fruit juice already contains the string ice, the second condition is redundant. You'd have to find a way to express "words" and not "substrings".
In the end, you might be better off implementing the parsing logic yourself.

How can I convert anonymous type to strong type in LINQ?

I have an array of ListViewItems ( ListViewItem[] ), where I store a SalesOrderMaster object in each ListViewItem.Tag for later reference.
I have some code that right now, goes through each ListViewItem safely casts the .Tag property into a SalesOrderMaster object, then adds that object to a collection of SalesOrders, only after checking to make sure the order doesn't already exist in that collection.
The process to compare sales orders is expensive, and I would like to convert this to a LINQ expression for clarity and performance. ( I also have the Parallel Extensions to .NET Framework 3.5 installed so I can use that to further improve LINQ performance)
So without further ado: This is what I have, and then what I want. ( what I want won't compile, so I know I am doing something wrong, but I hope it illustrates the point )
What I have: ( Slow )
foreach (ListViewItem item in e.Argument as ListViewItem[])
{
SalesOrderMaster order = item.Tag as SalesOrderMaster;
if ( order == null )
{
return;
}
if (!All_SalesOrders.Contains(order))
{
All_SalesOrders.Add(order);
}
}
What I want: ( Theory )
List<SalesOrderMaster> orders =
(from item in (e.Argument as ListViewItem[]).AsParallel()
select new { ((SalesOrderMaster)item.Tag) }).Distinct();
EDIT: I know the cast is cheap, I said the "Compare", which in this case translates to the .Contains(order) operation
EDIT: Everyone's answer was awesome! I wish I could mark more than one answer, but in the end I have to pick one.
EDIT : This is what I ended up with:
List<SalesOrderMaster> orders =
(from item in (e.Argument as ListViewItem[]) select (SalesOrderMaster) item.Tag).GroupBy(item => item.Number).Select(x => x.First()).ToList();
I see nobody has addressed your need to convert an anonymous type to a named type explicitly, so here goes... By using "select new { }" you are creating an anonymous type, but you don't need to. You can write your query like this:
List<SalesOrderMaster> orders =
(from item in (e.Argument as ListViewItem[]).AsParallel()
select (SalesOrderMaster)item.Tag)
.Distinct()
.ToList();
Notice that the query selects (SalesOrderMaster)item.Tag without new { }, so it doesn't create an anonymous type. Also note I added ToList() since you want a List<SalesOrderMaster>.
This solves your anonymous type problem. However, I agree with Mark and Guffa that using a parallel query here isn't you best option. To use HashSet<SalesOrderMaster> as Guffa suggested, you can do this:
IEnumerable<SalesOrderMaster> query =
from item in (ListViewItem[])e.Argument
select (SalesOrderMaster)item.Tag;
HashSet<SalesOrderMaster> orders = new HashSet<SalesOrderMaster>(query);
(I avoided using var so the returned types are clear in the examples.)
The part in that code that is expensive is calling the Contains method on the list. As it's an O(n) operation it gets slower the more objects you add to the list.
Just use a HashSet<SalesOrderMaster> for the objects instead of a List<SalesOrderMaster>. The Contains method of the HashSet is an O(1) operation, so your loop will be an O(n) operation instead of an O(n*n) operation.
Like Marc Gravell said, you shouldn't access the Tag property from different threads, and the cast is quite cheap, so you have:
var items = (e.Argument as ListViewItem[]).Select(x=>x.Tag)
.OfType<SalesOrderMaster>().ToList();
but then, you want to find distinct items - here you can try using AsParallel:
var orders = items.AsParallel().Distinct();

Categories