Dino
Dino

Reputation: 309

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;
}

Upvotes: 4

Views: 1893

Answers (2)

Priyank
Priyank

Reputation: 1384

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
                                                                         )
                                                                   ));

Upvotes: 0

Peter Duniho
Peter Duniho

Reputation: 70671

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.

Upvotes: 2

Related Questions