Linq expression not working as expected - c#

I want to select some values from a collection of strings, I wrote it one using LINQ and one using a foreach statement.
With the foreach version I get a list of about 300 entries.
List<string> res = new List<String>();
foreach (var l in anchors)
{
if (l.Attributes["href"] != null)
{
res.Add(l.Attributes["href"].Value);
}
}
With the LINQ version I get null:
IEnumerable<string> res2 = anchors.Select(l => l?.Attributes["href"]?.Value);

With the linq, you're getting null values as well and adding it to your enumerable. It's not identical to your foreach. Change it to:
IList<string> res2 = anchors.Where(l=>l.Attributes["href"] != null).Select(l => l.Attributes["href"].Value).ToList();

The .? syntax returns null if the item it is applied to returns null. In this case, null is added to the output.
With the check if (l.Attributes["href"] != null) it is not added to the output.
To mimic that in LINQ, add a Whereclause.

Related

Can I convert Dictionary loop inside of a foreach loop into a LINQ statement

If possible please help me convert these nested loops into a LINQ statement
Thank you very much for your help!
public static List<Term> GetTermsByName(this IEnumerable<Term> terms, Dictionary<string, string> termInfo)
{
List<Term> termList = new List<Term>();
foreach (Term term in terms)
{
foreach (var value in termInfo.Values)
{
if (term.Name == value)
{
termList.Add(term);
}
}
}
return termList;
}
Maybe Contains method is what you are after
Determines whether a sequence contains a specified element.
The following can be read as, Filter all Terms where the Term.Name exists in the dictionary Values
var values = termInfo.Values;
var result = terms.Where(term => values.Contains(term.Name));
.ToList();
// or
var result = terms.Where(term => termInfo.Values.Contains(term.Name));
.ToList();
You're losing the plot of the dictionary a bit here, don't you think? The speediness is in using the keys. However, you can still do better than a nested foreach or an inline linq equivalent with where and contains. Use a join to at least improve your efficiency.
var termList = (from term in terms
join value in termInfo.Values
on term.Name equals value
select term)
.Distinct() // if there are duplicates in either set
.ToList();

Why Lambda expression returns System.Linq.Enumerable+WhereSelectEnumerableIterator`2 as a result

I'm getting following string as result while returning a list of string using lambda expression:
System.Linq.Enumerable+WhereSelectEnumerableIterator`2[HOrg.ServiceCatalog.Contracts.Models.IOfferProperty,System.String]
My code is:
IList<string> offerIds = new List<string>();
foreach (var offer in offerProperties)
{
offerIds.Add(offer
.Where(x => x.PropertyDefinitionId == propertyDefinitionId)
.Select(x => x.OfferId)
.ToString());
}
Within foreach loop, offer variable contains expected values. But when I make condition using lambda expression, it returns System.Linq.Enumerable+WhereSelectEnumerableIterator`2 as a result.
When I search for this, I got a few suggestions like,
Copying results of lambda expressions in to a seperate list
Use ToList() for lambda expression then assign it to a result variable
and more suggestion. But no answer is helpful for me.
Is anybody know what's wrong in this code?
Instead of converting sequence to String:
// How can .Net convert sequence into string? The only way is to return type name
// which is unreadable System.Linq.Enumerable+WhereSelectEn...
offer
.Where(x => x.PropertyDefinitionId == propertyDefinitionId)
.Select(x => x.OfferId)
.ToString()
Join the items into a string
// Join items into string with "; " delimiter, e.g. "1; 2; 3; 4"
offerIds.Add(string.Join("; ", offer
.Where(x => x.PropertyDefinitionId == propertyDefinitionId)
.Select(x => x.OfferId)));
If you expect a single result for every offer, try:
IList<string> offerIds = new List<string>();
foreach (var offer in offerProperties)
{
offerIds.Add(offer.Where(x => x.PropertyDefinitionId == propertyDefinitionId).Select(x => x.OfferId).FirstOrDefault()?.ToString());
}
It seems to me that you want a collection of offerIds as a string, where multiple are attached to the offerproperties.
If so, then you are looking for the addrange function. Also move your ToString() call inside the select statement, not after it.
IList<string> offerIds = new List<string>();
foreach (var offer in offerProperties)
{
offerIds.AddRange(offer.Where(x => x.PropertyDefinitionId == propertyDefinitionId).Select(x => x.OfferId.ToString()));
}
Now for each offer, a selection of OfferId-strings is added to your offerIds IList

Get all results in a IQueryable instead of .FirstOrDefault

I have the following code in my controller which gets the first result of a search with multiple words. Similar to the answer to this question: ASP:NET MVC multiple words in search
var items = from s in db.Items select s;
if (!string.IsNullOrEmpty(searchString) && !searchString.Any(x => Char.IsWhiteSpace(x)))
{
searchString.Trim();
items = items.Where(s => s.keywords.Contains(searchString) || s.notes.Contains(searchString)
}
else if (!string.IsNullOrEmpty(searchString) && searchString.Any(x => Char.IsWhiteSpace(x)))
{
searchString.Trim();
string[] strings = searchString.Split(' ');
var finalItems = new List<Items>();
foreach (var splitString in strings)
{
finalItems.Add(items.FirstOrDefault(s => s.notes.Contains(splitString)
|| s.keywords.Contains(splitString));
}
items = finalItems.ToList().AsQueryable();
return View(items.ToPagedList(pageNumber, pageSize));
}
I want to get all the matching results instead of the first match. Now when I try to change the .FirstOrDefault line to:
finalItems.Add(items.Where(s => s.notes.Contains(splitString)
|| s.keywords.Contains(splitString)
I get a 'cannot convert from 'System.Linq.IQueryable(MyProject.Models.Items)' to 'MyProject.Models.Items.
I've tried changing my items assignment to:
var items = from s in db.Items
select s as IQueryable(Items);
That seems to fix the issue but then it breaks all my s.keywords.Contains or s.notes.Contains with the error 'IQueryable(Items) does not contain a definition for keywords or notes and no extension method keywords or notes accepting a first argument of type IQueryable(Items) could be found'.
FirstOrDefault() returns only one element of some type(MyProject.Models.Items in your case) or null.
Where() returns collection of items.
Add() method expects MyProject.Models.Items - one element, not collection.
You should use AddRange() to add collection:
finalItems.AddRange(items.Where(s => s.notes.Contains(splitString)
|| s.keywords.Contains(splitString);

How to convert foreach loop to a Linq query?

With the following structure
[[1,10],[2,20],[5,45],[10,34]]
this foreach loop finds the first element that matches "planYear". If planYear=5 then the third element value of "45" would be selected.
List<object> gifts = gifts;
foreach (List<object> item in gifts)
{
if (item[0] == planYear)
{
gift = Convert.ToDouble(item[1]);
break;
}
}
What would be an analogous Linq statement to achieve this same result?
var gift = gifts.Cast<List<object>>()
.Where(x => x[0] == planYear)
.Select(x => Convert.ToDouble(x[1]))
.FirstOrDefault();
If no matching entry has been found gift will be 0. If that's not what you want, use First() instead. This will throw an exception if no matching item exists.
This answer assumes - just like your foreach loop - that every item inside gifts is actually a List<object>. If even one item is of a different type, this code will throw an InvalidCastException. If this is a problem, use OfType instead of Cast.
var gift = Convert.ToDouble(
gifts.Cast<List<object>>().First(x => x[0] == planYear)[1]);

Update all objects except one in a collection using Linq

Is there a way to do the following using Linq:
foreach (var c in collection)
{
if (c.Condition == condition)
{
c.PropertyToSet = value;
// I must also check I only set this value to one minimum and only one element.
}
else
{
c.PropertyToSet = otherValue;
}
}
To clarify, I want to iterate through each object in a collection and then update a property on each object except for one element of my collection that should updated to another value.
At this moment I use a counter to check I set my value to one and only one element of my collection. I removed it from this example to let people suggest other solutions.
The original question without exception in collection is here
EDIT
I ask this question because I'm not sure it's possible to do it with LinQ. so your answers comfort my opinion about LinQ. Thank you.
You can use .ForEach to make the change, and .Single to verify only one element matches the condition:
// make sure only one item matches the condition
var singleOne = collection.Single(c => c.Condition == condition);
singleOne.PropertyToSet = value;
// update the rest of the items
var theRest = collection.Where(c => c.Condition != condition);
theRest.ToList().ForEach(c => c.PropertyToSet = otherValue);
I don't suggest you to implement this with Linq. Why? Because Linq is for querying, not for modification. It can return you objects which match some condition, or objects which don't match. But for updating those objects you still need to use foreach or convert query results to list and use ForEach extension. Both will require enumerating sequence twice.
So, simple loop will do the job:
foreach (var c in collection)
{
c.PropertyToSet = (c.Condition == condition) ? value : otherValue;
}
collection.Where(x => <condition>).ToList().ForEach(x => <action>);
Hacky way to use LINQ if you persist to use:
var result = collection.Select(c =>
{
c.PropertyToSet = c.Condition == condition ? value : otherValue;
return c;
});
But my recommendation, don't do this, you code actually get the best approach, for more readability, you can change:
foreach (var c in collection)
c.PropertyToSet = c.Condition == condition ? value : otherValue;
You can use a ternary operator in conjunction with a linq statement:
collection.ToList().ForEach(c => c.PropertyToSet = c.Condition == condition ? value : otherValue);
However I woud just use a regular foreach here to avoid converting the collection to a list.
Well, you could do:
var itemToSetValue = collection.FirstOrDefault(c => c.Condition == condition);
if(itemToSetValue != null)
itemToSetValue.PropertyToSet = value;
// Depending on what you mean, this predicate
// might be c => c != itemToSetValue instead.
foreach (var otherItem in collection.Where(c => c.Condition != condition))
{
otherItem.PropertyToSet = otherValue;
}
Now of course that's not a pure LINQ solution, but pure LINQ solutions are not appropriate for modifying existing collections.

Categories

Resources