Why is IEnumerable losing updated data? - c#

Could you explain to me why after executing the following code the Selected property is not updated to true?
The ListItem type used comes from System.Web.UI.WebControls namespace and is a class (not a struct.) I believed the FirstOrDefault function returns a reference to an instance which I can update and pass around in the items enumerable.
// produce list items out of the communities
IEnumerable<ListItem> items = communities.Select(community => new ListItem(community.Name, community.Id.ToString()));
// mark the right list item as selected, if needed
if (platform.CommunityId > 0)
{
string strCommunityId = platform.CommunityId.ToString();
ListItem selectedItem = items.FirstOrDefault(item => item.Value == strCommunityId);
if (selectedItem != null) selectedItem.Selected = true;
}
// now items do not store any updated item!
Is that because the enumerator is executed each time a foreach is called and thus creating new items instead of returning the set containing the item I updated?

The problem is that IEnumerable is not repeatable. You are performing the projection (community => new ListItem) every time it is enumerated - hence it is a new ListItem each time. Select is a non-buffered deferred projection.
You can fix everything here with the simple addition of a .ToList() to force the data into a single list;
var items = communities.Select(
community => new ListItem(community.Name, community.Id.ToString())
).ToList();
Now that the data is in the list, you can loop over the list any number of times - it'll always be the same items, and changes will be retained.

Your problem is that
IEnumerable<ListItem> items = communities
.Select(community => new ListItem(community.Name, community.Id.ToString()));
creates an IEnumerable that's lazily evaluated -- that is, every time it is enumerated, the original communities sequence is re-enumerated and your Select projection is re-executed per item in that sequence.
If you stick a .ToList() at the end, changing the line to:
IEnumerable<ListItem> items = communities
.Select(community => new ListItem(community.Name, community.Id.ToString()))
.ToList();
you will observe a different result. While it is still an IEnumerable, it will no longer be a lazily evaluated one, and the changes you make in it will be observable in later iterations over the same IEnumerable.

It happens, because you use Select:
IEnumerable<ListItem> items = communities
.Select(community => new ListItem(community.Name, community.Id.ToString()));
which creates new objects every time you iterate through items.

I think Marc Gravell's answer is the right one, but you could avoid this confusion and do it in a single line (possibly leading to another kind of confusion). ;)
// produce list items out of the communities
IEnumerable<ListItem> items = communities.Select(community =>
new ListItem(community.Name, community.Id.ToString())
{
Selected = community.Id == platform.CommunityId
});

Related

C# linq how to get all the first elements of a nested dynamic list?

I have a 2D nested list List<list<dynamic>>, I'd like to get first elements of every second level list and form a new List<dynamic>.
I know .first() gets the first element of a list, but how can I apply it on multiple sub lists?
Thanks in advance.
Proper and efficient way of doing this is as follows:
mainList.Select(subList => subList?.First()).OfType<dynamic>();
This will take care of null lists and null elements
If you want to select all the elements of sublists try following
mainlist.Select(subList => subList).OfType<List<dynamic>>();
This will only take care of null lists
IEnumerable s = listOfLists.Where(lists => lists.Any())
.Select(array => array.First());
Explanation:
First fetch the list of arrays where there are any records by using lists => lists.Any()
Then select the first item from each list and return.
try this please if you want to take the first two elements of each list, and the more elements you want you can increase the integer in the .Take().
List<List<string>> originalList = new List<List<string>>()
{
new List<string>(){"1","1","1"},
new List<string>(){"2","2"},
};
var FirtTwoElementsList = originalList.Select(x => x.Take(2)).ToList();
List<string> FinalList = new List<string>();
foreach (var item in FirtTwoElementsList)
{
FinalList.AddRange(item.ToList<string>());
}

C# SkipWhile(Item => Item == null) still returns null items

I have a list of items:
List<Item> ItemList = new List<Item>;
Sometimes the list is only partially full or certain indices are not occupied and therefore when I iterate through the list using a foreach, it gives an error because the item is null. I want to reduce that list to those items which actually have a value. This is what I'm trying:
var FullItems = ItemList.SkipWhile(Item => Item == null).ToList();
But when I check the FullItems list, it still contains the items from ItemList that are null, so I'm just getting back the entire list that I started with.
Help?
What you are looking for here is:
var FullItems = ItemList.Where(Item => Item != null).ToList();
As per the docs on SkipWhile():
Bypasses elements in a sequence as long as a specified condition is true and then returns the remaining elements.
This is not the behavior you are looking for (I have bolded the actual behavior that you don't seem to expect/desire).
SkipWhile(i => i == null) will skip until the first non-null item. Items after that first one which are null are still ignored.
Use Where(i => i != null) to select all items that are not null.

Remove child list item when checking grandchild list using Linq

The following code shows how I am assigning data into IEnumerable<UnCompletedJobDetailsBO>.
There is a list (IEnumerable<UnCompletedJobDetailsBO>) that has another list (List<JobDetailsBO>), with that child list (List<JobDetailsBO>) having a list on it. But the AllocationDetailList only ever has one list item.
public IEnumerable<UnCompletedJobDetailsBO> GetControlDetails(DateTime startDate)
{
var controlDetails =
(from booking in db.BookingDetail
where booking.BookingDateTime >= startDate
orderby booking.DocketNo
select new UnCompletedJobDetailsBO()
{
CustomerName = booking.Customer.Name,
CompanyName = booking.CompanyDetail.Name,
JobList =
(from job in db.BookingJob.Where(x => x.BookingID == booking.BookingID) //get job list
select new JobDetailsBO()
{
JobID = job.JobID,
JobType = job.JobType,
ItemName = job.ItemName,
AllocationDetailList =
(from jobAllocationDetail in db.JobAllocation
join returnUnCollected in db.JobReturnUnCollected
on jobAllocationDetail.JobAllocationDetailID
equals returnUnCollected.JobAllocationDetailID
into returnJob
from returnUnCollected in returnJob.DefaultIfEmpty()
where (jobAllocationDetail.Booking.BookingID == booking.BookingID)
select new AllocationBO()
{
JobUnCollectedID = returnJob.JobUnCollectedID,
JobType = jobAllocationDetail.JobType,
CurrentStatus = jobAllocationDetail.CurrentStatus,
}).DefaultIfEmpty().ToList(),
}).DefaultIfEmpty().ToList(),
}).ToList();
return controlDetails;
}
I want to remove the JobList item if the inner list (AllocationDetailList) item satisfies the condition below. Sometimes AllocationDetailList may be null, so I check that also. But when I write below query, it does not remove that particular JobList item that satisfies the condition. Thanks in advance.
public List<UnCompletedJobDetailsBO> RemovePODFromSelectedList(
List<UnCompletedJobDetailsBO> unCompletedJobDetailsBO)
{
unCompletedJobDetailsBO
.SelectMany(y => y.JobList)
.ToList()
.RemoveAll(x => ((x.AllocationDetailList[0] != null) ?
x.AllocationDetailList[0].JobType == "D" &&
x.AllocationDetailList[0].JobUnCollectedID == null &&
x.AllocationDetailList[0].CurrentStatus == 5 :
x.AllocationDetailList.Count > 1));
return unCompletedJobDetailsBO;
}
Without a good, minimal, complete code example, I'm not sure that any performance concern can be addressed. It's hard enough to fully understand the question as it is, but without being able to actually test the code, to reproduce and observe a specific performance concern, it's hard to know for sure where your concern specifically lies, never mind how to fix it.
That said, from the code you posted, it is clear why items are not being removed from the list. The basic issue is that while the SelectMany() method does have the effect of allowing you to enumerate all of the elements from all of the different JobList objects as a single enumeration, the elements are enumerated as a new enumeration.
When you call ToList(), you are creating a whole new list from that new enumeration, and when you call RemoveAll(), you are only removing elements from that new list, not the lists from which they originally came.
You say you can get it to work with a for loop. I assume you mean something like this:
public List<UnCompletedJobDetailsBO> RemovePODFromSelectedList(
List<UnCompletedJobDetailsBO> unCompletedJobDetailsBO)
{
foreach (var item in unCompletedJobDetailsBO)
{
item.JobList.RemoveAll(x => ((x.AllocationDetailList[0] != null) ?
x.AllocationDetailList[0].JobType == "D" &&
x.AllocationDetailList[0].JobUnCollectedID == null &&
x.AllocationDetailList[0].CurrentStatus == 5 :
x.AllocationDetailList.Count > 1));
}
return unCompletedJobDetailsBO;
}
Note: there is no need to return unCompletedJobDetailsBO. That entire object is unchanged, never mind the variable. The only thing the code is modifying is each individual JobList object within the passed-in object's members. I.e. the above method could actually have a return type of void, and the return statement could be removed entirely.
It is possible you could speed the code up by removing the elements in a different way. The List<T>.RemoveAll() method is in fact reasonably efficient, with O(n) cost. But it still involves copying all of the data in the list after the first element that is removed (so that all the elements are shifted down in the list). If you have to have the list ordered, this may be as good as you can do, but if not, you could process the removal differently, or use a different data structure altogether, something unordered where removal of one or more elements costs less.
But again, without more details and without a good example to work with addressing that particular issue doesn't seem practical here.
The condition
x.AllocationDetailList[0] != null
will throw exception if there is no item in the AllocationDetailList. Instead you need to check
x.AllocationDetailList!=null && x.AllocationDetailList.Count>0.
Also .ToList() after SelectMany in your code will create a new list and items will be removed from that new list instead of unCompletedJobDetailsBO. You need to modify the remove function as below
unCompletedJobDetailsBO.ForEach(y => y.JobList.RemoveAll(x => ((x.AllocationDetailList != null && x.AllocationDetailList.Count>0)
?
x.AllocationDetailList[0].JobType == "D"
&& x.AllocationDetailList[0].JobUnCollectedID == null
&& x.AllocationDetailList[0].CurrentStatus == "5"
:
x.AllocationDetailList.Count > 1
)
));

why ToList() doesnt work in linq?

i am having a issue while sorting data with more than a field
here is sample code i have used :
var Item = from itm in DB.Items
select new Item
{
};
return Item.ToList().OrderBy(e => e.ExpiryDate).ToList();
above code does'nt show any error but also doesn sort with expirydate field :(
if i use like this
return Item.OrderBy(e => e.ExpiryDate).ToList();
this gives a error that sql doesnt contain translation for orderby
any help
thanks in advance
I think there are a couple things going on here. First, you've named your result variable the same as the class you're creating in the select. Probably doesn't make much difference, but it's confusing. Let's change it:
var items = from itm in DB.Items
select new Item
{
};
Next, your DB.Items context is already built of Item objects. You don't need to create new ones. In the above code, your new Item objects are empty anyway.
var items = from itm in DB.Items
select itm;
If you want to order them right away, you can do that too:
var items = from itm in DB.Items
orderby itm.ExpiryDate
select itm;
If you need that as a List, you can do that in the same line:
var items = (from itm in DB.Items
orderby itm.ExpiryDate
select itm).ToList();
Now items will be a concrete List of Item objects (List<Item>).
Your code creates instances of Item using the default constructor, without passing anything that you get from the DB to it. That is why you get a list of identical empty items; sorting them has no effect.
Remove the first Select to make it work:
var Item = DB.Items; // No "Select ..."
return Item.ToList().OrderBy(e => e.ExpiryDate).ToList();
Your first ToList forces the data into memory, where OrderBy can be applied. You can replace it with an AsEnumerable() call as well, to avoid creating a list in memory for the second time:
return Item.AsEnumerable().OrderBy(e => e.ExpiryDate).ToList();

Adding IEnumerable<Type> to IList<Type> where IList doesn't contain primary key - LAMBDA

I have an IList<Price> SelectedPrices. I also have an IEnumerable<Price> that gets retrieved at a later date. I would like to add everything from the latter to the former where the former does NOT contain the primary key defined in the latter. So for instance:
IList<Price> contains Price.ID = 1, Price.ID = 2, and IEnumerable<Price> contains Price.ID = 2, Price.ID = 3, and Price.ID = 4. What's the easiest way to use a lambda to add those items so that I end up with the IList containing 4 unique Prices? I know I have to call ToList() on the IList to get access to the AddRange() method so that I can add multiple items at once, but how do I select only the items that DON'T exist in that list from the enumerable?
I know I have to call ToList() on the IList to get access to the AddRange() method
This is actually not safe. This will create a new List<T>, so you won't add the items to your original IList<T>. You'll need to add them one at a time.
The simplest option is just to loop and use a contains:
var itemsToAdd = enumerablePrices.Where(p => !SelectedPrices.Any(sel => sel.ID == p.ID));
foreach(var item in itemsToAdd)
{
SelectedPrices.Add(item);
}
However, this is going to be quadratic in nature, so if the collections are very large, it may be slow. Depending on how large the collections are, it might actually be better to build a set of the IDs in advance:
var existing = new HashSet<int>(SelectedPrices.Select(p => p.ID));
var itemsToAdd = enumerablePrices.Where(p => !existing.Contains(p.ID));
foreach(var item in itemsToAdd)
{
SelectedPrices.Add(item);
}
This will prevent the routine from going quadratic if your collection (SelectedPrices) is large.
You can try that:
var newPrices = prices.Where(p => !SelectedPrices.Any(sp => sp.ID == p.ID));
foreach(var p in newPrices)
SelectedPrices.Add(p);
I know I have to call ToList() on the IList to get access to the AddRange() method so that I can add multiple items at once
ToList will create a new instance of List<Price>, so you will be modifying another list, not the original one... No, you need to add the items one by one.
Try yourEnumerable.Where(x => !yourList.Any(y => y.ID == x.ID)) for the selection part of your question.
If you want to add new elements to the existing list and do that in a most performant way you should probably do it in a conventional way. Like this:
IList<Price> selectedPrices = ...;
IEnumerable<Price> additionalPrices = ...;
IDictionary<int, Price> pricesById = new Dictionary<int, Price>();
foreach (var price in selectedPrices)
{
pricesById.Add(price.Id, price);
}
foreach (var price in additionalPrices)
{
if (!pricesById.ContainsKey(price.Id))
{
selectedPrices.Add(price);
}
}

Categories