C# while loop overwriting scoped variable with each iteration - c#

I have 2 lists, allCharges and selectedCharges, where each item in selectedCharges is contained in allCharges.
I want to create a third list (called charges) that is a copy of all charges, but with a boolean property of "Selected" for each item set to true if the charge is in the list of selected charges.
This is fine if I am only doing this once; however, I am trying to perform the same operation five times, rewriting "charges" each time while saving "charges" to my "typeSettingsList".
IList<TypeSettings> typeSettingsList = new List<TypeSettings>();
var typeId = 1;
while (typeId < 6)
{
var links = _linkChargeTypeRepo.Query().Where(l => l.TypeId == typeId);
var allCharges = _chargeRepo.GetAll().ToList();
var selectedCharges = links.Select(l => l.ChargeType).ToList();
selectedCharges.ForEach(c => c.Selected = true);
var nonSelectedCharges = allCharges.Except(selectedCharges).ToList();
nonSelectedCharges.ForEach(c => c.Selected = false);
var charges = nonSelectedCharges.Concat(selectedCharges).ToList();
var settingsWithType = new TypeSettings
{
Type = _typeRepo.Get(typeId),
Charges = charges
};
typeSettingsList.Add(settingsWithType);
typeId++;
}
return settingsWithType;
My problem is that each "Charges" object in my typeSettingsList ends up getting overwritten with the charges object that is created on the last iteration, even though the variable is declared inside the while loop (and should therefore be a new object reference with each iteration).
Is this just an incorrect understanding of how variables inside while loops should work?
How can I make it so my "charges" list isn't overwritten with each iteration?

The problem is that you do not "materializecharges`: this
var charges = nonSelectedCharges.Concat(selectedCharges);
is an IEnumerable<Charge> with deferred evaluation. By the time you get to evaluate Charges from the typeSettingsList, the loop is over, so enumerating the IEnumerable<Charge> returns the results for the last value of typeId (i.e. typeId = 5).
Add ToList() to fix this problem:
var charges = nonSelectedCharges.Concat(selectedCharges).ToList();
Edit: Another problem is that links is using typeId, which is modified in the loop. You should define a new variable inside the loop to capture the state of typeId during the specific iteration, like this:
var typeId = 1;
while (typeId < 6)
{
var tmpTypeId = typeId;
var links = _linkChargeTypeRepo.Query().Where(l => l.TypeId == tmpTypeId);
...
}

Related

How to click a link and open it in a new tab Selenium WebDriver C#

I am having trouble figuring out how to open a link in a new tab using selenium Webdriver. I am getting stale exceptions in the loops because the pages are not correct after the first iteration. So my idea is to open the link in a new tab, do all the operations I want to do on that tab, and switch back to the old tab to continue the loop, but I am not too sure how to open these tabs and manage them.
string year = this.yearTextBox2.Text;
string semester = this.semesterTextBox2.Text;
int numCourses = (int)this.numEnrollments.Value;
int count = 0;
string URL = GetURL(year, semester, "index");
_driver.Navigate().GoToUrl(URL);
//var result = _driver.FindElement(By.XPath("//*[#id=\"uu-skip-target\"]/div[2]/div"));
var results = _driver.FindElements(By.CssSelector(".btn.btn-light.btn-block"));
// Loop through each department
foreach (var r in results)
{
// Make sure not to include the letter link
// Click on this department and get the list of all courses
r.Click();
var result2 = _driver.FindElement(By.Id("class-details"));
var results2 = result2.FindElements(By.XPath("./*[#class=\"class-info card mt-3\"]"));
var courseCount = 0;
// Loop through each course in the department
foreach (var r2 in results2)
{
// Stop the process once reached the amount of courses needed to be scraped
if (count >= numCourses)
break;
Course c = new Course();
c.year = year;
c.semester = semester;
var header = r2.FindElement(By.TagName("h3"));
if (header != null)
{
// Gets the course (CS 2420)
string courseNum = header.Text.Split('-')[0].Trim().ToUpper();
string[] depAndNum = courseNum.Split(' ');
// Separate department and number
c.department = depAndNum[0];
c.number = depAndNum[1];
// Get the course title
string text = header.Text.Split('-')[1].Trim();
c.title = text.Substring(4);
// Check if the course is a lecuture/seminar, if not then continue.
var list = result2.FindElement(By.CssSelector(".row.breadcrumb-list.list-unstyled"));
if (CourseIsLecture(list.FindElements(By.TagName("li"))))
{
c.count = courseCount;
GetCourseInformation(r2, c);
}
else
{
courseCount++;
continue;
}
}
// Increment the course count on this department page
courseCount++;
// Increment total course count
count++;
}
}
You can perform a click while holding the control key to force open the link in a new tab. You can use actions API for the same.
Actions action = new Actions(webDriver);
action.KeyDown(Keys.LeftControl).Click(r).KeyUp(Keys.LeftControl).Build().Perform();
However, I believe you might still get a stale reference exception when you come back to tab 0 and continue looping over results collection.If this happens to be the case, you can retrieve the count first and convert your foreach loop to a while/for loop and lookup your results collection every time inside while/for loop and then use results[i] to process that element further.
Another option could be to wrap your loop in a retry block e.g. using Polly framework and lookup results collection again in case of stale reference and retry the entire thing.

Possible Multiple Enumeration Scenario. How to handle? [duplicate]

This question already has answers here:
Fastest way to get matching items from two list c#
(4 answers)
Closed 1 year ago.
public IEnumerable<ComputedData> Compute (IEnumerable<DataSet> data, IEnumerable<Variations> variationData)
{
// I need to create one ComputedData instance for each item inside the IEnumerable<DataSet> data
// DataSet has properties: int ID, int Valu1, int Value2
// ComputedData has properties: int ID, int Result, int Variation
// variationData has properties: int ID, int Variation
var computedDate = data.Select (i => new ComputedData ()
{
ID = i.ID,
Result = i.value1 + i.value2
});
// ISSUE is here
foreach (var item in computedDate )
{
var id = item.ID;
// I need to find the corresponding element
// (with same ID) on IEnumerable<Variations> variationData
// and assign item.Variation =
// But getting Possible Multiple Enumeration warning
// and item.Variation become zero always !
}
}
Issue is the use of foreach loop. Is there any other way to solve this issue. Even though the code is working, item.Variation is always zero. That shouldn't be the case.
Any suggestions ?
The warning is to ensure that every time you iterate through the items in computeDate you don't re-execute this:
var computedDate = data.Select(i => new ComputedData()
{
ID = i.ID,
Result = i.value1 + i.value2
}).
which could, in turn, re-execute whatever method produced IEnumerable<DataSet> data.
There are few ways to deal with that. An easy one is to add this:
var computedDate = data.Select(i => new ComputedData()
{
ID = i.ID,
Result = i.value1 + i.value2
}).ToArray(); // <---- Add this
After this line of code enumerates the items in data the results are stored in an array, which means you can safely enumerate it again and again without executing this Select or anything else unexpected.

Need help pulling certain data out of a list

I have a call Center Application that displays calls in queue in a bunch of other data to our analysts and managers. I'm trying to display the next three people in the queue to receive a call. I was able to put together the code below and reference max3 as an itemsource in a listbox but it doesn't actually display the names of the people next. when you add a breakpoint on max3 it shows the three agents that are next but it also shows all of their data, time in queue, extension number, stuff like that. i need to know how to only display their name.
List<NewAgent> newAgentList = new List<NewAgent>();
List<Tuple<String, TimeSpan>> availInItems = new List<Tuple<string, TimeSpan>>();
foreach (var item in e.CmsData.Agents)
{
NewAgent newAgents = new NewAgent();
newAgents.AgentName = item.AgName;
newAgents.AgentExtension = item.Extension;
newAgents.AgentDateTimeChange = ConvertedDateTimeUpdated;
newAgents.AuxReasons = item.AuxReasonDescription;
newAgents.LoginIdentifier = item.LoginId;
newAgents.AgentState = item.WorkModeDirectionDescription;
var timeSpanSince = DateTime.Now - item.DateTimeUpdated;
newAgents.AgentDateTimeStateChange = timeSpanSince;
newAgentList.Add(newAgents);
if (item.WorkModeDirectionDescription == "AVAIL-IN")
{
availInItems.Add(Tuple.Create(newAgents.AgentName, timeSpanSince));
}
availInItems.Sort((t1, t2) => t1.Item2.CompareTo(t2.Item2));
}
Occurs after above code:
var availInAgents = newAgentList
.Where(ag => ag.AgentState == "AVAILIN") .ToList();
availInAgents.Sort((t1, t2) =>
t1.AgentDateTimeStateChange.CompareTo(t2.AgentDateTimeStateChange));
var minTimeSpanAgent = availInAgents.FirstOrDefault();
var maxTimeSpanAgent = availInAgents.LastOrDefault();
var min3 = availInAgents.Take(3).ToList();
var max3 = availInAgents.Skip(availInAgents.Count - 3);
max3.Reverse();
This is where my problem exists, it displays the info in the screen shot below. I only need the AgentName out of it and i don't know how to only access that piece of information. Please assist with this.
nextInLine.itemsource = max3.ToString();
Use the .Select() method to project a new type from your query.
nextInLine.itemsource = max3?.Select(x => x?.AgentName).FirstOrDefault() ?? string.Empty;
It will take the first agent in max3 and retrieve only the string AgentName, assigning it to itemsource.
The ? in this case is the null propagation operator. If max3 is null, it will return null before evaluating the .Select() which, in conjunction with the Null Coalescing operator (??), will set the itemsource to an empty string. It repeats this process if any item in your max3 list is null, or if AgentName itself is null.
you can use Select from Linq
var agentNamesFromMax3 = max3.Select(m => m.AgentName);

Create instance of object every nth time through a loop

Apologies if I'm struggling to word this properly. OOP is not my expertise but I'm very much trying to learn.
How do I create an instance of an object on say, every third iteration of a loop?
Within a loop, I need to assign values to an object, but the property to assign a value to will depend on the result of a case statement. Once each property of the object has been assigned, I then need to add that object to a list of objects of the same type.
If I create the object before the loop is entered, then my list just contains the same result over and over again, because (I've read) that the list only contains a reference to the object, and if the object is then changed, so does the list.
If I create the object within the loop, then obviously, I'll get a new object each time with just one of the properties assigned to it by the time it adds it to the list. The list would contain different results, but only the last property would be assigned, as a new object is created each time.
What I assumed you could therefore do was create a new object whenever all of the properties had a value assigned to it (or at the start, when none had). So, since my object has three properties, each time through the loop, I would like to add a new object whenever an int iCounter was 0, add the values, and increment iCounter, then when iCounter is 3, set to 0. However, when I attempt to create an object inside of an if statement, the rest of the program doesn't see the object exists.
I also assumed, I could maybe attempt some kind of macro substitution, which is what I would normally resort to in Fox, however, (I've read) that this is a big no-no in c#.
Any ideas?
try
{
cProducts Product = new cProducts();
SqlConn2.Open();
rdr2 = SqlComm2.ExecuteReader();
int iScanLine = 0;
while (rdr2.Read())
{
iScanLine++;
Product.product = rdr2["product"].ToString();
Product.sOrder = rdr2["order_id"].ToString();
switch (rdr2["detail"].ToString())
{
case "Quantity":
Product.quantity = Convert.ToInt16(rdr2["display_value"]) ;
break;
case "Option":
Product.Option = rdr2["display_value"].ToString();
break;
case "Size":
Product.Size = rdr2["display_value"].ToString();
break;
}
if (iScanLine == 3)
{
lProducts.Add(Product);
thisPage.sProducts.Add(lProducts[lProducts.Count() - 1]);
iScanLine = 0;
}
}
}
You could just change this bit:
if (iScanLine == 3)
{
lProducts.Add(Product);
thisPage.sProducts.Add(Product); //<-- We know the object just added is still in Product
iScanLine = 0;
Product = new cProducts(); //<-- Create a new object to start populating
}
Also, I know that .NET framework is quite new, being only a decade old, but you might consider reading the Naming Guidelines:
X DO NOT use Hungarian notation.
Looks like you have table with four columns, where each product represented in three consecutive rows
product | order_id | detail | display_value
A X Quantity 5
A X Option Foo
A X Size XL
B X Quantity 2
...
And you are trying to read products. I suggest you to store current product name and compare it with last product name. If name is changed, then you are reading data of next product, thus you can create new product and add it to list of products:
IDataReader reader = SqlComm2.ExecuteReader();
List<Product> products = new List<Product>();
Product product = null;
while (reader.Read())
{
var name = reader["product"].ToString();
if (product == null || product.Name != name) // check if new product
{
product = new Product(); // create new product
product.Name = name; // fill name
product.OrderId = reader["order_id"].ToString(); // and order
products.Add(product); // add to products
}
object value = reader["display_value"]; // get value from row
switch (reader["detail"].ToString())
{
case "Quantity":
product.Quantity = Convert.ToInt16(value);
break;
case "Option":
product.Option = value.ToString();
break;
case "Size":
product.Size = value.ToString();
break;
}
}
As you can see, I also refactored naming - PascalCase for properties, camelCase for local variables, no Hungarian notation. Also new names for properties introduced - Product.Name instead of odd Product.Product, OrderId instead of sOrder.
Use the Modulus operator to check whether the iterating variable is divisible by expected nth value or not
if(value % 3 == 0)
{
//do stuff
}
value++;
You are repeatedly adding the same Product to your list, and never create a new one. When you get to the end of your loop, it'll appear as though you've only got a single item in there.
After you've added your item (within the if (iScanLine == 3)), I suspect you want to create a new item: Product = new cProducts().
Also, I would like to reference this particular comment that you make in your question:
If I create the object before the loop is entered, then my list just
contains the same result over and over again, because (I've read) that
the list only contains a reference to the object, and if the object is
then changed, so does the list.
The following code will result in 5 separate objects being added to a list:
List<cProducts> list = new List<cProducts>();
cProducts Product = new cProducts();
for (int i = 0; i < 5; i++)
{
list.Add(Product);
Product = new cProducts();
}
You are correct that the list only contains references to the objects - but you are not changing any of the objects; you are creating new ones. This is a fundamental programming principle, and I'd suggest you take the time to understand how it works before moving on.
Not sure if I understand completely but the following loop whould use a counter to execute every third time
int isThirdTime = 0; //Test condition for third time equality
while (true) //neverending loop
{
if (isThirdTime == 3)//test for 3rd time
{
// add to list
isThirdTime = 0; //reset counter
}
isThirdTime++; // Increase the counter
}

What is difference between System.Linq.Enumerable.WhereListIterator & System.Linq.Enumerable.WhereSelectListIterator?

What is difference between System.Linq.Enumerable.WhereListIterator & System.Linq.Enumerable.WhereSelectListIterator?
One difference I hav noticed is Type WhereListIterator reflects changes on collection object but WhereSelectListIterator does not
I will make it more clear for eg.
I hav a scenario where I fetch my Domain Object from Repository
var buckets = testRepository.GetBuckets(testIds);
Then I select certain buckets from the above collection inside a loop
var bucketsForTest = buckets.Where(bucket => bucket.TestID == test.testId);
Then I change a single property of all the Bucket Objects inside the method of LooserTrafficDisributor object.
ITrafficDistributor distributor = new LooserTrafficDisributor(bucketsForTest);
IEnumerable<Bucket> updatedBuckets = distributor.Distribute(test.AutoDecision);
Constructor of LooserTrafficDisributor
public LooserTrafficDisributor(IEnumerable<Bucket> allBuckets)
{
this.allBuckets = allBuckets;
}
The distribute method inside LooserTrafficDistributor looks like this
private IEnumerable<Bucket> DistributeTraffic(bool autoDecision)
{
// allBuckets is class variable in LooserTrafficDistributor object which is set through constructor shown above .
// Omitted other details
allBuckets.Where(bucket=> bucket.IsControl == false).ToList()
.ForEach(bucket => bucket.TrafficPercentage += 10 ));
return allBuckets
}
After this I can see the reflected changes inside the IEnumerable updatedBuckets collection.
But if I do this i.e. instead of fetching Bucket collection from repository do a select & then Update all the Bucket objects in similar manner as follows
var bucketsForTest = testRows.Where(testrow => testrow.url == url.url).Select(currRow => new Bucket
{
TestID = currRow.TestId,
BucketID = currRow.BucketId,
BucketName = currRow.c_bucket_name,
TrafficPercentage = Convert.ToInt32(currRow.i_bucket_percentage),
IsControl = currRow.b_is_control,
IsEnabled = currRow.b_enabled,
UpdatedAdminId = currRow.i_updated_admin_id,
LogAsSection = currRow.i_log_as_section
}) ;
ITrafficDistributor distributor = new LooserTrafficDisributor(bucketsForTest);
IEnumerable<Bucket> updatedBuckets = distributor.Distribute(test.AutoDecision, strategy.GetStatisticallySignificantLoosingBucketIds());
I can't get the changes reflected inside the IEnumerable updatedBuckets collection.
Infact I debugged inside the DistributeTraffic methods even there the changes were not reflected after each loop round.
.Where() makes an IEnumerable of your items containing all elements which fullfil the where criteria. If you run a .Select() on that result set, you will get a IEnumerable of new elements you've created in the select-statement. So changes to the original elements will not reflect on the new elements.
In your example you create for every Bucket in the original list fullfilling your where criteria a new Bucket object, copying the content from the original bucket to the new Bucket.

Categories