LINQ equivalent of List<T>.Find()? - c#

I'm looking at some code that takes an IEnumerable<T> and converts it to a List<T> so it can use List<T>.Find(predicate):
var myEnumerable = ...;
var myList = new List<T>(myEnumerable);
var match = myList.Find(value => value.Aaa == aaa && value.Bbb == bbb);
Is there a way to rewrite this using the LINQ extension methods that has the same effect, but without building an extra List<T> as an intermediate step?
The FirstOrDefault(source, predicate) extension method looks like a good candidate, but trying to figure out if it's exactly equivalent to Find is making my head hurt.

Just for reference, here is a table of some old .NET 2 style List<> instance methods, and their equivalent extension methods in Linq:
METHOD IN List<> METHOD IN Linq
------------------------------------------------------------------------------------------
list.Contains(item) query.Contains(item)
list.Exists(x => x.IsInteresting()) query.Any(x => x.IsInteresting())
list.TrueForAll(x => x.IsInteresting()) query.All(x => x.IsInteresting())
list.Find(x => x.IsInteresting()) query.FirstOrDefault(x => x.IsInteresting())
list.FindLast(x => x.IsInteresting()) query.LastOrDefault(x => x.IsInteresting())
list.FindAll(x => x.IsInteresting()) query.Where(x => x.IsInteresting())
list.ConvertAll(x => x.ProjectToSomething()) query.Select(x => x.ProjectToSomething())
Of course some of them are not entirely equivalent. In particular Linq's Where and Select use deferred execution, while FindAll and ConvertAll of List<> will execute immediately and return a reference to a new List<> instance.
FindLast will often be faster than LastOrDefault because FindLast actually searches starting from the end of the List<>. On the other hand LastOrDefault(predicate) always runs through the entire sequence (starting from the first item), and only then returns the most "recent" match.

The LINQ equivelent would be to use FirstOrDefault:
var match = myEnumerable.FirstOrDefault(value => value.Aaa == aaa && value.Bbb == bbb);

Or you can do the following way:
var match = myEnumerable.Where(value => value.Aaa == aaa && value.Bbb == bbb)
.FirstOrDefault();

Related

LINQ to Entities does not recognize the method (Trying to get Previous and Next record)

I am trying to get the Previous and Next Record, but I am getting an exception at run time. What am I doing wrong? I found this solution to my problem on the internet. I get the basic idea, but I am not very experienced with LINQ.
LINQ to Entities does not recognize the method 'System.Linq.IQueryable1[CXW__DAQ___Web.Models.Report] SkipWhile[Report(System.Linq.IQueryable1[CXW_DAQWeb.Models.Report],System.Linq.Expressions.Expression1[System.Func2[CXW__DAQ_Web.Models.Report,System.Boolean]])' method, and his method cannot be translated into a store expression.
here is my relevant code.
var NextRecord = db.Reports.OrderBy(i => i.ID)
.SkipWhile(i => i.ID != id)
.Skip(1)
.First();
int nextID = NextRecord.ID;
var PrevRecord = db.Reports.OrderBy(i => i.ID)
.Reverse()
.SkipWhile(i => i.ID != id)
.Skip(1)
.Last();
int prevID = PrevRecord.ID;
What the error message is trying to tell you is that LINQ to Entities has no way to translate the IEnumerable.SkipWhile() method into SQL. You'll first have to force the remainder of the query to use LINQ to Objects first instead:
var nextRecord =
db.Reports.OrderBy(i => i.ID)
.AsEnumerable()
.SkipWhile(i => i.ID != id)
.Skip(1)
.First();
The obvious downside here being that your entire table is going to be ordered and then read into memory.

Remove duplicates of a List, selecting by a property value in C#?

I have a list of objects that I need some duplicates removed from. We consider them duplicates if they have the same Id and prefer the one whose booleanValue is false. Here's what I have so far:
objects.GroupBy(x => x.Id).Select(x => x.Where(y => !y.booleanValue));
I've determined that GroupBy is doing no such grouping, so I don't see if any of the other functions are working. Any ideas on this? Thanks in advance.
You can do this:
var results =
from x in objects
group x by x.Id into g
select g.OrderBy(y => y.booleanValue).First();
For every Id it finds in objects, it will select the first element where booleanValue == false, or the the first one (if none of them have booleanValue == false).
If you prefer fluent syntax:
var results = objects.GroupBy(x => x.Id)
.Select(g => g.OrderBy(y => y.booleanValue).First());
Something like this should work:
var result =
objects.GroupBy(x => x.Id).Select(g =>
g.FirstOrDefault(y => !y.booleanValue) ?? g.First())
This assumes that your objects are of a reference type.
Another possibility might be to use Distinct() with a custom IEqualityComparer<>.
This partially answers the question above, but I justed need a really basic solution:
objects.GroupBy(x => x.Id)
.Select(x => x.First())
.ToArray();
The key to getting the original object from the GroupBy() is the Select() getting the First() and the ToArray() gets you an array of your objects, not a Linq object.

Distinct operator on List<string>

I'm trying to get distinct string values out of an Ax repository, but I'm getting a lot of identical strings out (strings only contains numbers)
var ret = context.XInventTransBackOrder
.Where(i => i.BatchRouteId != "")
.Select(i => i.BatchRouteId)
.Distinct()
.ToList();
Where am I going wrong?
Have you tried
var ret = context.XInventTransBackOrder
.Where(i => i.BatchRouteId != "")
.Select(i => i.BatchRouteId)
.ToList();
ret = ret
.Distinct()
.ToList();
If the BatchRouteId was a XElement, for instance, then probably an object reference comparison would be performed. In that case change the code to
var ret = context.XInventTransBackOrder
.Where(i => i.BatchRouteId != null && !String.IsNullOrEmpty(i.BatchRouteId.Value))
.Select(i => i.BatchRouteId.Value)
.Distinct()
.ToList();
UPDATE #1
Note that some types implement implicit conversions making you think they were another type. You can pass a string to a XName parameter without explicit casting, and the string will automatically be converted to XName.
UPDATE #2
According to a comment of nk2003dec the context is LinqToDynamicsAx. I don't know this interface but probably it does not implement Distinct. What you can to in such a case, is to change the context form a XY-LINQ to Object-LINQ by using the System.Linq.Enumerable.AsEnumerable<TSource> extension method
var ret = context.XInventTransBackOrder
.Select(i => i.BatchRouteId)
.Where(id => id != "")
.AsEnumerable()
.Distinct()
.ToList();
I also inverted Select and Where as this simplifies the access to BatchRouteId
X++ does not have a distinct operator. The deferred execution will try to execute on ToList() and will fail because of this.

How can I specify to use Linq ThenBy clause only when there is a tie?

I have a linq query (not database-related) with OrderBy and ThenBy
var sortedList = unsortedList
.OrderBy(foo => foo.Bar) //this property access is relatively fast
.ThenBy(foo => foo.GetCurrentValue()) //this method execution is slow
getting foo.Bar is fast, but executing foo.GetCurrentValue() is very slow. The return value only matters if some members have equal Bar values, which happens rarely but important to be considered in case it happens. Is it possible to choose to only execute the ThenBy clause when it's necessary to tie-break in case of equal Bar values? (i.e. will not be executed if foo.Bar values are unique).
Also, actually Bar is also a bit slow, so it is preferred not to invoke it twice for the same object.
Since you are not in a database, and you need a tight control over the sorting, you could use a single OrderBy with a custom IComparer that accesses only what it needs, and does not perform unnecessary evaluations.
This is a bit clumsy, but I'm sure it can be improved - maybe it won't be done in one linq statement, but it should work:
var sortedList2 = unsortedList
.OrderBy(foo => foo.Bar)
.GroupBy(foo => foo.Bar);
var result = new List<Foo>();
foreach (var s in sortedList2)
{
if (s.Count() > 1)
{
var ordered = s
.OrderBy(el => el.GetCurrentValue());
result.AddRange(ordered);
}
else
{
result.AddRange(s);
}
}
UPDATE:
We can argue if that's an improvement, but it looks more concise at least:
var list3 = (from s in sortedList2
let x = s.Count()
select x == 1
? s.Select(el => el)
: s.OrderBy(el => el.GetCurrentValue()))
.SelectMany(n => n);
UPDATE2:
You can use Skip(1).Any() instead of Count() - this should avoid the enumeration of the whole sequence I guess.
var query = unsortedList
.GroupBy(foo => foo.Bar)
.OrderBy(g => g.Key)
.SelectMany(g => g.Skip(1).Any() ? g.OrderBy(foo => foo.GetCurrentValue()) : g);
This has the obvious downside of not returning IOrderedEnumerable<Foo>
I changed Joanna Turban's solution and developed the following extension method:
public static IEnumerable<TSource> OrderByThenBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> orderBy, Func<TSource, TKey> thenBy)
{
var sorted = source
.Select(s => new Tuple<TSource, TKey>(s, orderBy(s)))
.OrderBy(s => s.Item2)
.GroupBy(s => s.Item2);
var result = new List<TSource>();
foreach (var s in sorted)
{
if (s.Count() > 1)
result.AddRange(s.Select(p => p.Item1).OrderBy(thenBy));
else
result.Add(s.First().Item1);
}
return result;
}
Try this one
var sortedList = unsortedList.OrderBy(foo => foo.Bar);
if(some_Condition)
{
sortedList = sortedList.OrderBy(foo => foo.GetCurrentValue());
}

Understanding the processing pipeline

Take
var query = Process.GetProcesses()
.OrderBy(p => p.WorkingSet64)
.ThenByDescending(p => p.Threads.Count);
.Where(p => p.ProcessName.Length < 9);
It works fine. Take
var query = Process.GetProcesses()
.OrderBy(p => p.WorkingSet64)
.ThenByDescending(p => p.Threads.Count);
//.Where(p => p.ProcessName.Length < 9);
query = query.Where(p => p.ProcessName.Length < 9);
This does not work. I do not understand why the first method works. In my mind these queries are the same. ThenByDescending returns IOrderedEnumerable<T> which is piped into Where(). The first method should not work because Where only works with IEnumerable<T>. Alas...it does work.
How does this processing pipeline function?
The difference is because of a misunderstanding of the var keyword and LINQ queries.
var (C# reference)
Using the var keyword is the same as specifying the same type as the right side of the assignment. It does not mean that you can assign any type to the variable.
In LINQ queries, most of the basic expressions return an IEnumerable, but it many cases, they do not return just an IEnumerable. Instead, they return an type that inherits from IEnumerable.
In this case, you are doing the equivalent of this:
IEnumerable<Process> query = Process.GetProcesses()
.OrderBy(p => p.WorkingSet64)
.ThenByDescending(p => p.Threads.Count);
.Where(p => p.ProcessName.Length < 9);
and
IOrderedEnumerable<Process> query = Process.GetProcesses()
.OrderBy(p => p.WorkingSet64)
.ThenByDescending(p => p.Threads.Count);
// Won't work because Where doesn't return an IOrderedEnumerable.
query = query.Where(p => p.ProcessName.Length < 9);
The reason that the first snippet works is because IOrderedEnumerable inherits from IEnumerable, so you can use it as such.
To fix the problem in the second example, you need to explicitly declare query as IEnumerable<Process>.

Categories

Resources