Avoid Multiple Nested For Each Loops - c#

I am new to C# and taking my first course at a University. I think this is an issue with instantiation but here goes with what I am after.
I am wanting to get the value from te.ED w/o having to go through the multiple for each loops, as if the answer is "No" then there is no need to go through the loops and extract multiple data elements (not being shown). Is there a way to check that value BEFORE going through all of the for each loops?
Code is here
TR reply = service.track(request);
foreach (CTD ctd in reply.CTD)
{
foreach (TD td in ctd.TD)
{
if (td.Events != null)
{
foreach (TE te in td.Events)
{
if (te.TimestampSpecified)
{
//This is where the field I am after exists
if (te.ED == "YES")
Console.WriteLine("The answer is yes");
else
Console.WriteLine("The answer is no");
}
}
}
}
}
Per the comment from #Anis Programmer - I believe you are wanting to see the TD element from the class CTD. If that is the case - see below
[System.Xml.Serialization.XmlElementAttribute("TD")]
public TD[] TD {
get { return this.tdf; }
set { this.tdf = value; }
}
Per the answer from #Neel below - I am very close with the syntax
var result = reply.CTD.SelectMany(c1 => c1.TD)
.SelectMany(c2 => c2.Events.Select(c3 => c3.TimestampSpecified));
foreach (var ltr in result)
Console.WriteLine(ltr)
Now the issue is that the foreach loop makes two passes, and the value returned from both is True
What do I need to change in this syntax?

I think you can use a LinQ in a foreach like this:
foreach (
var te in from ctd in reply.CTD
from td in ctd.TD
where td.Events != null
from te in td.Events
where te.TimestampSpecified
select te)
{
Console.WriteLine(te.ED == "YES" ? "The answer is yes" : "The answer is no");
}

What I assumed from the example you have posted that you want to avoid multiple nested foreach loop.
You can use linq to shorten the same.Here is how using lamda expression.
var result = reply
.SelectMany(c1 => c1.CTD)
.SelectMany(c2 => c2.TD)
.SelectMany(c3 => c3.Events.Select(c4 => c4.TimestampSpecified));
Now you just loop on the result and compare with ED value.

Related

Why is this C# code not functioning as expected?

This is a C# code to enter 10 names, sort them in ascending order and print them with the condition if either of the elements are "Nobody" or "Somebody" it should skip that name when printing. The issue with my code is that it doesn't skip those 2 mentioned words and prints them as well, I don't understand why.
Sample Input:
Ravi
Somebody
Tanvir
Ramesh
Nobody
Ani
Nobody
Vishwanath
Somebody
Nitin
Sample Output:
Ani
Nitin
Ramesh
Ravi
Tanvir
Vishwanath
using System;
using System.Collections;
namespace LearnCsharp
{
class NamesWithArrayList
{
public static void Main(string[] args)
{
//Update the code below
ArrayList alObj;
alObj = new ArrayList();
int max=10;
string item="";
for(int i=0;i<max;i++)
{
alObj.Add(Console.ReadLine());
}
alObj.Sort();
foreach (string item1 in alObj)
{
if(alObj.Contains("Somebody")){}
else if(alObj.Contains("Nobody")){}
else
Console.WriteLine(item1);
}
}
}
}
You're checking if the list contains "Somebody" each time rather than if the current value is "Somebody".
ArrayList is essentially a collection of objects, so string comparison is not being used when calling Contains. It's instead using object reference comparison, and since your inputs are different objects that the string constants, the comparison always fails.
So a version which uses string comparisons would be:
foreach (string item1 in alObj)
{
if(item1 == "Somebody"){}
else if(item1 == "Nobody"){}
else
Console.WriteLine(item1);
}
which could be simplified as:
foreach (string item1 in alObj)
{
if(item1 != "Somebody" && item1 != "Nobody")
Console.WriteLine(item1);
}
You need to check '''item1''' if it is your searched string and you need to put some code for the if statement what your app should do than.
The problem is that on the if(alObj.Contains("Somebody")){} line you're asking if the original alObj ArrayList contains the string "Somebody". Your foreach loop should be re-written as follows:
foreach (string item1 in alObj)
{
if(item1 == "Somebody")
{
}
else if (item1 == "Nobody")
{
}
else
Console.WriteLine(item1);
}
However this could be improved further by merging those the "Somebody" and "Nobody" cases into one:
foreach (string item1 in alObj)
{
if(item1 == "Somebody" || item1 == "Nobody")
{
// Do nothing
}
else
Console.WriteLine(item1);
}
Finally this is a bit weird looking, the real intent of the code is "Only output the item if its not equal to 'Somebody' and not equal to 'Nobody'", which is best expressed like so:
foreach (string item1 in alObj)
{
if(item1 != "Somebody" && item1 != "Nobody")
Console.WriteLine(item1);
}
In 2020 you don't need to use ArrayList. In fact the only reason it is still exists probably is backward compatibility. Use List<string>. If you do that, you can do this
using System.Linq;
myList.Where(x => x != "Nobody" && x != "Somebody")
.Sort(StringComparer.OrdinalIgnoreCase)
.ToList()
.ForEach(x => Console.WriteLine(x));
Another interesting way is this
using System.Linq;
var checkList = new List<string>(){ "Nobody", "Somebody" };
myList.Except(checkList)
.Sort(StringComparer.OrdinalIgnoreCase)
.ToList()
.ForEach(x => Console.WriteLine(x));
In this instance you work on the lever of 2 lists. You will retrieve only items that don't match.

Should a method call to get data be before a foreach loop?

I currently have a snippet of code that will act upon every user found in Active Directory and as I was looking at it I started to wonder if it is better to have the method call done before the foreach loop or if it is ok the way it is. I have tested using principalSearcher.FindAll()) both inside and out of the loop and can't notice a difference but then there is not really a large enough data set to see one, so I am wondering about this more from a best practice situation.
foreach (var user in principalSearcher.FindAll())
{
var employeeID = db.Employees
.Where(employee => employee.ADUserName == user.SamAccountName && employee.EndDate == null)
.Select(employee => employee.ID)
.FirstOrDefault();
if (employeeID > 0)
{
var updatedEmployee = db.Employees.Find(employeeID);
updatedEmployee.EndDate = DateTime.Today;
db.SaveChanges();
}
}
Note: principalSearcher is of type System.DirectoryServices.AccountManagement.PrincipalSearcher
The foreach will get compiled to (roughly)
//begin decomposition of foreach (var user in principalSearcher.FindAll())
var temp = principalSearcher.FindAll();
var enum = temp.GetEnumerator();
while(enum.MoveNext())
{
var user = enum.Current;
// body of foreach block
}
// end decomposition
versus
// local variable outside of foreach
var allPrincipals = principalSearcher.FindAll();
//begin decomposition of foreach (var user in allPrincipals)
var enum = allPrincipals.GetEnumerator();
while(enum.MoveNext())
{
var user = enum.Current;
// body of foreach block
}
// end decomposition
So whether you declare a variable outside of the foreach and use it or not makes no practical difference.
*Note that I do not include other artifacts like try/finally or casting/boxing since they are not germane to the question.

Linq Call - There is already an open DataReader associated with this Command which must be closed first

I have some code which when run an exception of type 'EntityCommandExecutionException' is raised.
The line which Visual Studio points to:
else if (item.FirstOrDefault().InspectionEquipmentTypes.Any())
The inner details of the exception say that:
There is already an open DataReader associated with this Command which must be closed first.
My question is the line which raised the error is not trying to use a database/datareader (to my knowledge) so I am unsure why this exception is being generated.
Edit:
public static IEnumerable<IGrouping<string,Entities.Inspection>> GetUnscheduledBatchInspections(Entities.EntityModel context)
{
var results = context.Inspections.Where(w =>
w.InspectionBatchNo != null
&& w.IsCancelled == false
&& !w.CalendarItems.Any()
&& w.Duration.HasValue).GroupBy(g => g.InspectionBatchNo);
return results;
}
Calling method:
private void MapBatchInspectionsToViewModel(ref SchedulerViewModel viewModel)
{
var batchInspections = SchedulerManager.GetUnscheduledBatchInspections(this.Context);
foreach (var item in batchInspections)
{
var bigi = new BatchInspectionGridItem();
if (item.Any())
{
bigi.BatchInspectionNo = item.First().InspectionBatchNo;
if (item.FirstOrDefault().EquipmentTypeID != null)
{
bigi.EquipmentTypeName = item.FirstOrDefault().EquipmentType.Description;
}
else if (item.FirstOrDefault().InspectionEquipmentTypes.Any())
{
bigi.EquipmentTypeName = string.Join(" / ", item.FirstOrDefault().InspectionEquipmentTypes.Select(s => s.EquipmentType.Description));
}
bigi.CustomerName = item.First().CustomerSite.Customer.CustomerName;
bigi.CustomerID = item.First().CustomerSite.Customer.CustomerID;
bigi.NumberOfInspections = item.Count();
bigi.TotalDuration = item.Sum(s => s.Duration);
}
viewModel.BatchInspectionGridViewModel.Add(bigi);
}
}
Here's what happens: while you loop through batchInspections the database reader is reading this collection from the database. Within the loop you do new database reads by the numerous First(OrDefault) calls, the Sum and the Count. That causes the exception 'There is already an open DataReader...'.
As said by George Lica, you can probably solve this by setting MultipleActiveResultSets=True in your connection string.
Or you can finish reading batchInspections before the loop starts itereating by...
foreach (var item in batchInspections.ToList())
But it is far more efficient to first collect the data you're going to need and then loop through them:
foreach (var item in batchInspections
.Select(b => new
{
First = b.FirstOrDefault(),
Count = b.Count(),
Sum = b.Sum(s => s.Duration)
} )
.ToList())
{
var bigi = new BatchInspectionGridItem();
if (item.Any())
{
bigi.BatchInspectionNo = item.First.InspectionBatchNo;
if (item.First.EquipmentTypeID != null)
{
bigi.EquipmentTypeName = item.First.EquipmentType.Description;
}
else if (item.First.InspectionEquipmentTypes.Any())
{
bigi.EquipmentTypeName = string.Join(" / ", item.First.InspectionEquipmentTypes.Select(s => s.EquipmentType.Description));
}
bigi.CustomerName = item.First.CustomerSite.Customer.CustomerName;
bigi.CustomerID = item.First.CustomerSite.Customer.CustomerID;
bigi.NumberOfInspections = item.Count;
bigi.TotalDuration = item.Sum;
}
viewModel.BatchInspectionGridViewModel.Add(bigi);
}
I hope that SchedulerManager.GetUnscheduledBatchInspections returns an IQueryable, so that the subsequent Select into the anonymous type will be translated into SQL.
It must be said though that activating MARS is nearly always a good idea with Entity Framework, because lazy loading has a way of causing this exception.
This happens when you make queries in a nested way.
item.FirstOrDefault().InspectionEquipmentTypes.ToList().Any()
may work. I am not sure though. Try simplifying the nested queries. For example don't make queries like:
items.Where(/*some condition*/).Any();
instead make
items.Any(/*some condition*/);
If you really want to have nested queries ( i don't recommend that, i would rather do separate queries and link entities using some hashing data structures) and you are using sql server, you actually have an alternative: activate MARS. To activate it, just add in your connection string MultipleActiveResultSets=True. For more details follow this link : https://msdn.microsoft.com/en-us/library/h32h3abf(v=vs.110).aspx

Finding an element with Linq and lambda expressions: but doesn't work

Have a look to the following example:
The first solution, using the foreach, works pretty well and easily. But I was trying to write it using Linq and I could not achieve this result. I made some attempts but no one succeeded.
I expect to find just one element.
The problem is not at runtime: I don't know very well the Linq sintax and so I don't know how to get the element called PlacedSelection (the foreach structure clarifies where I'm looking for it). Instead in my attempt I could get the PlacedCategory elements.. but I don't need this..
PlacedSelection ActualSelection = null;
foreach (var placedCategory in Model.Coupon.Categories)
{
foreach (PlacedSelection placedSelection in placedCategory.Value.Selections)
{
var pp = placedSelection.EventId;
if (pp == Model.EventId)
{
ActualSelection = placedSelection;
break;
}
}
}
//IEnumerable<KeyValuePair<string, PlacedCategory>> p = Model.Coupon.Categories(c => c.Value.Selections.Any(s=> s.EventId == Model.EventId));
It looks like you want:
PlacedSelection actualSelection = Model.Coupon.Categories
.SelectMany(cat => cat.Value.Selections)
.FirstOrDefault(selection => selection.EventId == Model.EventId);
Any would be used if you were trying to find the category, but you're trying to find the selection, by the looks of it.

Passing data out

I'm currently having a bit of a nightmare with a foreach loop. In a nutshell, what I am trying to do is split a string and then filter this data based on the string. I then need to bind the said data to a control of filter it further down the line. So far, I have the following code
if (Session["Contract"] != null)
{
string[] contract = Session["Contract"].ToString().Split(',');
foreach (string i in contract)
{
if (i.ToString() != "")
{
data = data.Where(x => x.Term.Trim().ToUpper().Contains(i.ToString().Trim().ToUpper()));
}
}
}
LV_Jobs.DataSource = data;
LV_Jobs.DataBind();
Now when looping through, the filtering works fine, but once you are finished with one item, the data variable is cleared? Obviously I need to pass "data" back out of the foreach loop. Can anyone point me in the direction of how to do this???
You are resetting data every time the loop iterates. Try this (depending on what data is)
var filteredData = new List<string>();
if (Session["Contract"] != null)
{
filteredData = Session["Contract"].ToString().Split(',').Join(
data,
i => i.ToString().Trim().ToUpper(),
x => x.Term.Trim().ToUpper(),
(i, x) => x);
}
LV_Jobs.DataSource = filteredData;
LV_Jobs.DataBind();
Simply collect the needed data out in a list of results or any other data structure declared outside of the for/foreach scope.
Your data variable is not cleared. Instead, in the last iteration of the foreach where i.ToString() != "" your Where() condition is not true. So data becomes an empty list.
You can break out of the foreach when you found what you were looking for.

Categories