Mohit
Mohit

Reputation: 39

Convert nested for each loop to LINQ

I have a for each loop to get data which is very time consuming.any suggestion to convert this to linq. Thanks in advance.

iListReport = obj.GetClosedReports();
string sRepType ="";
foreach (ReportStatisticsInfo item in reportStatistic)
                {
                    sRepType = item.ReportName.Trim();
                    IList<string> lastClosedReport = new List<string>();
                    foreach (TaskListInfo taskInfo in iListReport)
                    {
                        string reportName = taskInfo.DocumentName.Trim();
                        if (string.Compare(sRepType, reportName, true) == 0)
                        {
                            if (taskInfo.ActionID == Convert.ToInt16(ReportAction.Close) && !lastClosedReport.Contains(taskInfo.DocumentID))
                            {
                                iClosedreportCount += 1;
                                lastClosedReport.Add(taskInfo.DocumentID);
                            }
                        }
                    }
                }

Upvotes: 0

Views: 309

Answers (2)

Doctor Jones
Doctor Jones

Reputation: 21654

Here you go. I've done a pretty literal translation of your code into LINQ which will hopefully help you to see how I've converted it.

Note the use of the let keyword which allows you to declare a range variable (which allows you to perform your trim once and then use the result in multiple places).

Also note the use of group by at the bottom of the LINQ query to ensure we only take the first occurence of each documentID.

IList iListReport = obj.GetClosedReports();

var query = from item in reportStatistic
            let sRepType = item.ReportName.Trim()
            from taskInfo in iListReport
            let reportName = taskInfo.DocumentName.Trim()
            where string.Compare(sRepType, reportName, true) == 0
               && taskInfo.ActionID == Convert.ToInt16(ReportAction.Close)
            //here's how we make sure we don't get the same documentID twice
            //we group by the id and then take the first
            group taskInfo by taskInfo.DocumentID into grouping
            select grouping.First().DocumentID;

var lastClosedReport = query.ToList();

iClosedreportCount = lastClosedReport.Count;

How to convert a foreach loop to LINQ

Here are some comparisons of your code against LINQ version to help you out if you've got to do a conversion again sometime. Hopefully this will help anyone else out there that has got to convert a foreach loop to LINQ.

1. foreach and from

You can perform a straight swap of the foreach clause for a LINQ from clause. You can see that this:

foreach (ReportStatisticsInfo item in reportStatistic)

has become this:

from item in reportStatistic

2) Variable declaration and the let keyword

When you declare variables within your foreach, you can swap them out for the LINQ let statement. You can see that this declaration:

sRepType = item.ReportName.Trim();

has become:

let sRepType = item.ReportName.Trim()

3) if statements and the where clause

Your if statements can go inside the where clause. You can see that the following two if statements:

if (string.Compare(sRepType, reportName, true) == 0)

if (taskInfo.ActionID == Convert.ToInt16(ReportAction.Close)

have become this where clause

where string.Compare(sRepType, reportName, true) == 0
   && taskInfo.ActionID == Convert.ToInt16(ReportAction.Close)

4) Using group by to remove duplicates.

It's all been quite simple so far because everything has just been a straight swap. The most tricky part is the bit of code where you prevent duplicates from appearing in your result list.

if (taskInfo.ActionID == Convert.ToInt16(ReportAction.Close) 
 && !lastClosedReport.Contains(taskInfo.DocumentID))
{
    iClosedreportCount += 1;
    lastClosedReport.Add(taskInfo.DocumentID);
}

This is tricky because it's the only part that we have to do a bit differently in LINQ.

Firstly we group the 'taskInfo' by the 'DocumentID'.

group taskInfo by taskInfo.DocumentID into grouping

Then we take the first taskInfo from each grouping and get it's ID.

select grouping.First().DocumentID;

A note about Distinct

A lot of people try to use Distinct to get rid of duplicates. This is fine when we're using primitive types, but this can fail when you're using a collection of objects. When you're working with objects Distinct will do a reference comparison of the two objects. This will fail to match objects that are different instances but happen to have the same ID.

If you need to remove duplicates based upon a specific property within an object, then the best approach is to use a group by.

Upvotes: 1

Teejay
Teejay

Reputation: 7471

With LINQ you'll get a single IEnumerable<string> with duplicates

from item in reportStatistic
from taskInfo in iiListReport
where (string.Compare(item.ReportName.Trim(), taskInfo.DocumentName.Trim(), true) == 0)
   && taskInfo.ActionID == Convert.ToInt16(ReportAction.Close)
select taskInfo.DocumentID

You can then Distinct().GroupBy(d => d.taskInfo)

Upvotes: 1

Related Questions