Why is LINQ not letting me OrderBy one of my data points? - c#

I have this code:
return inventoryItems
.Where(i => 0 < String.Compare(i.ID, ID))
.Take(CountToFetch);
...but I want to order the results, like so:
return inventoryItems
.Where(i => 0 < String.Compare(i.ID, ID))
.Take(CountToFetch)
.OrderBy(i.pksize);
...however, the final i is red/out of scope. Why? Trying to position the OrderBy() prior to the Take() makes no difference.

return inventoryItems.Where(i => 0 < String.Compare(i.ID, ID))
.Take(CountToFetch)
.OrderBy(i => i.pksize);
And maybe you should change OrderBy and Take order to make results predictable:
return inventoryItems.Where(i => 0 < String.Compare(i.ID, ID))
.OrderBy(i => i.pksize)
.Take(CountToFetch);

There's no i in the OrderBy.
You want OrderBy(i => i.pksize) (or indeed x => x.pksize, whatever => whatever.pksize, etc.)
The sort of syntax that allows from x in something where x.IsOkay orderby x.Priority etc. uses the same variable label all the way through, but it gets turned into more than one lambda expression, which are each separate from each other. (something.Where(x => x.IsOkay).OrderBy(x => x.Priority), but they need to each be full expressions.

It looks like you've tried to use the i you declared in Where. If you look closely at the brackets, you should see that it is actually no longer in scope within the OrderBy. So the first problem is you're trying to use an out-of-scope variable.
OrderBy actually needs to be able to check the property on each element in the IEnumerable, so it doesn't make sense to only pass it the value of one element's property. It needs to know how to get the property for each element, which is why you typically pass in a lambda expression, anonymous delegate or method.
As the others pointed out, you can solve the problem by using an lambda expression like you did in Where:
.OrderBy(i => i.pksize)

Related

LINQ Query OrderByDescending not recognizing lambda expression

I'm fairly new to LINQ, but I think I'm getting the hang of it.
I'm trying to group a select statement, then order it descending. I've got pretty far just looking through other questions, but when I try to attach the OrderByDescending() function, lambda expressions I try to add are not recognized by intellisense.
var test = db.UserPokemons.Where(x => x.PkmnDataId == pokemon.Id && x.StepsToHatch == 0)
.GroupBy(n => n.User1.Country).OrderByDescending();
So, for example, .OrderbyDescending(x => x.User1.Country) does not contain a definition for User1.
Is this to do with the ordering of my statements? I think it's because I'm calling GroupBy before OrderBy, but I can't wrap my head around how to fix it.
How can I order my groups?
Oh! Nearly forgot - I only want the top 3 countries, so is there an easy way to restrict that as well?
How to use GroupBy and OrderByDescending in the same LINQ function
How to select only the top/first 3 groups
Thanks for any help!
I think this is what you want:
var test = db.UserPokemons.Where(x => x.PkmnDataId == pokemon.Id && x.StepsToHatch == 0).
GroupBy(n => n.User1.Country).Select(x=>x.First()).OrderByDescending().Take(3);
var countriesInOrder = db.UserPokemons.Where(x => x.PkmnDataId == pokemon.Id && x.StepsToHatch == 0).
GroupBy(n => n.User1.Country).OrderByDescending(x => x.Key).Take(3).ToArray();
Got it - the lambda expression needs to go to the x.Key after grouping.
(Thanks jitender!)

Set several class values using LINQ expression

I have the following two LINQ statements which set different values in the same item in a list
List<MyClass> myList = GetList();
myList.Where(x => x.Name == "someName").Select(x => x.MyArray = someList.ToArray()).ToList();
myList.Where(x => x.Name == "someName").Select( x => x.AnotherValue = GetValue()).ToList();
Is it possible to combine this so both are set in the one expression?
myList
.Where(x => x.Name == "someName")
.ToList()
.ForEach(x => {
x.MyArray = someList.ToArray();
x.AnotherValue = GetValue();
});
Why are you calling ToList() at the end of each of those expressions and discarding the result?
Also, Jon Skeet is right that this is an abuse of LINQ, and especially so in your original form: It's explicit that LINQ expressions aren't even necessarily expected to be fully enumerated. The fact that you needed those ToList() calls to make anything happen should have given you a grave and queasy sense that you were misusing a language feature. When you have to do something weird to use your chosen construct instead of the usual way of doing it, finish getting it to work (because weird is cool), and then go back and redo it the boring, lame way before you check it in.
What advantage do you see in the LINQ + ForEach() version above, compared to this version?
foreach (var x in myList.Where(x => x.Name == "someName"))
{
x.MyArray = someList.ToArray();
x.AnotherValue = GetValue();
}
The old-style loop version is shorter, instantly understandable because it's the default idiom, and IMO cleaner. You don't have to do everything with LINQ.
N.B., ForEach() isn't LINQ; it's a member of List<T>. That's why you have to call ToList() to use it.
Just use the lambda operator to pass an entire lambda expression defined inside a
{...} block:
myList.Where(x => x.Name == "someName").Select(x => { x.MyArray = someList.ToArray(); x.AnotherValue = GetValue(); return x;}).ToList();

Does LINQ know how to optimize "queries"?

Suppose I do something like
var Ordered = MyList.OrderBy(x => x.prop1).ThenBy(x => x.prop2);
Does MyList.OrderBy(x => x.prop1) return the filtered list, and then does it further filter that list by ThenBy(x => x.prop2)? In other words, is it equivalent to
var OrderedByProp1 = MyList.OrderBy(x => x.prop1);
var Ordered = OrderedByProp1.OrderBy(x => x.prop2);
???
Because obviously it's possible to optimize this by running a sorting algorithm with a comparator:
var Ordered = MyList.Sort( (x,y) => x.prop1 != y.prop1 ? x.prop1 < y.prop1 : ( x.prop2 < y.prop2 ) );
If it does do some sort of optimization and intermediate lists are not returned in the process, then how does it know how to do that? How do you write a class that optimizes chains of methods on itself? Makes no sense.
Does MyList.OrderBy(x => x.prop1) return the filtered list
No. LINQ methods (at least typically) return queries, not the results of executing those queries.
OrderBy just returns an object which, when you ask it for an item, will return the first item in the collection given a particular ordering. But until you actually ask it for a result it's not doing anything.
Note you can also get a decent idea as to what's going on by just looking at what OrderBy returns. It returns IOrderedEnumerable<T>. That interface has a method CreateOrderedEnumerable which:
Performs a subsequent ordering on the elements of an IOrderedEnumerable according to a key.
That method is what ThenBy uses to indicate that there is a subsequent ordering.
This means that you're building up all of the comparers that you want to be used, from the OrderBy and all ThenBy calls before you ever need to generate a single item in the result set.
For more specifics on exactly how you can go about creating this behavior, see Jon Skeet's blog series on the subject.

Filter in linq with ID's in a List<int>

I need do a filter that request data with a parameter included in a list.
if (filter.Sc.Count > 0)
socios.Where(s => filter.Sc.Contains(s.ScID));
I try on this way but this not work, I tried also...
socios.Where( s => filter.Sc.All(f => f == s.ScID));
How I can do a filter like this?
socios.Where(s => filter.Sc.Contains(s.ScID));
returns a filtered query. It does not modify the query. You are ignoring the returned value. You need something like:
socios = socios.Where(s => filter.Sc.Contains(s.ScID));
but depending on the type of socios the exact syntax may be different.
In addition to needing to use the return value of your LINQ .Where(), you have a potential logic error in your second statement. The equivalent logic for a .Contains() is checking if Any of the elements pass the match criteria. In your case, the second statement would be
var filteredSocios = socios.Where( s => filter.Sc.Any(f => f == s.ScID));
Of course if you can compare object-to-object directly, the .Contains() is still adequate as long as you remember to use the return value.

and operator in Linq and select distinct values using linq

I am new to .net. I have a form in which there are two comboboxes cbProduct and cbBrandName and also a label lblPrice.
I am trying to implement the below code but it is showing blue scribbles to &&.
(Error: operator '&&' cannot be applied to operands of type 'lambda expression' and 'lambda expression')
I tried the below code: (not working)
lblPrice.Text = string.Empty;
lblPrice.Text = doc.Descendants("items"
).Where((x => x.Element("productname"
).Value.Equals(cbProduct.SelectedItem.ToString())) && /*blue scribbles to '&&'*/
(y => y.Element("brandname").Value.Equals(cbBrandName.SelectedItem.ToString()
))).Select(k => k.Element("price"
).Value).ToString();
My other question is that i want to make the selected values of cbProduct as distinct. The below code takes all the values instead of distinct values:
cbProduct.Items.AddRange(doc.Descendants("items"
).Select(x => x.Element("productname").Value
).ToArray<string>());//adds all products
cbProduct.SelectedIndex = 0;
giving any one answer is ok
Please assist me
Thanks in advance
It looks like you are passing 2 lambdas to the Where function and trying to logical-and (&&) them together. You can't do that. The && has to occur inside the Where lambda. Or you can chain 2 Where functions together. Something like this:
lblPrice.Text = doc.Descendants("items")
.Where(x => x.Element("productname").Value.Equals(cbProduct.SelectedItem.ToString()) &&
x.Element("brandname").Value.Equals(cbBrandName.SelectedItem.ToString()))
.Select(k => k.Element("price").Value).ToString();
The other issue I see is you are ending your query with a select, but never actually enumerating it. You probably want to do something like this:
lblPrice.Text = doc.Descendants("items")
.Where(x => x.Element("productname").Value.Equals(cbProduct.SelectedItem.ToString()) &&
x.Element("brandname").Value.Equals(cbBrandName.SelectedItem.ToString()))
.Select(k => k.Element("price").Value)
.FirstOrDefault();
Which will return the string you are looking for, or null if nothing exists (so you probably want to skip the final .ToString() call in this case, since you are already returning a string from Select and .ToString() on a null will throw an exception).
For the first question, it looks like you just want to select the one price. This code will work, assuming that the item is found by the .Single(). It will throw otherwise, in which case you should use .SingleOrDefault() and check for null on the found item.
lblPrice.Text =
doc.Descendants("items")
.Single(x => x.Element("productname").Value == cbProduct.SelectedItem.ToString() &&
x.Element("brandname").Value == cbBrandName.SelectedItem.ToString())
.Element("price").Value;
For the second question, you need to close off your .Select with a bracket, then you can call .Distinct() and .ToArray() to filter to distincts and project the result to string[]. I've also thrown an .OrderBy() in there, as there's nothing more annoying than a ComboBox in a random order. Try this:
cbProduct.Items.AddRange(doc.Descendants("items")
.Select(item => item.Element("productname").Value)
.Distinct()
.OrderBy(item => item)
.ToArray());

Categories

Resources