Nested foreach loops with side effects for Entity Framework - c#

I have problem with nested foreach loops because of following example:
var someList = new List<SimpleC>();
simpleA = database.SimpleA.ToList();
simpleB = database.SimpleB.ToList();
foreach (var a in simpleA)
{
foreach (var b in simpleB)
{
var c = new simpleC();
c.simpleAId = a.Id;
c.simpleAName = a.Name;
c.simpleBName = b.Name;
someList.Add(c);
}
}
return someList;
The problem: imagine first iteration it goes to first foreach loop then to second foreach loop it initiates new object, maps values and adds newly initiated object to new list, but when second iteration comes instead of going back to first foreach loop it continues with second foreach loop and it loops through second until it finishes, I understand that's how C# works, but I need side effect for it to continue to first foreach loop to get new values.

You would be much better served by not trying to iterate this manually, establishing a relationship and querying it as a join set of entities:
var results = database.SimpleA
.Join(database.SimpleB,
a => a.ID,
b => b.Post_ID, /
(a, b) => new { colA = a, colB = b })
.Select new SimpleA(){
Property1 = a.Col1,
Property2 = b.Col3,
Property4 = a.Col2
}).ToList();
This is only mostly functional as a starting point. You might need a left outer join instead.
Another option, if A and B have a fk constraint in the database, then you may use Navigation properties instead of a join.
If for whatever reason, your loop is required we can try and breakdown the issue:
foreach (var a in simpleA)
{
foreach (var b in simpleB) // There is no relationship here!
{
var c = new simpleC();
c.simpleAId = a.Id;
c.simpleAName = a.Name;
c.simpleBName = b.Name;
someList.Add(c);
}
}
You could try using the index, but that is very likely to cause issues.
Instead:
foreach (var a in simpleA)
{
var matchedB = simpleB.Where(x => x.Id == a.Id).FirstOrDefault();
var c = new simpleC();
c.simpleAId = a.Id;
c.simpleAName = a.Name;
c.simpleBName = matchedB.Name;
someList.Add(c);
}

I think, if I'm understanding your question, is that you don't want it to loop through in a nested fashion, but only loop once. To do that you'd do it like this.
for(int i = 0; i < simpleA.Count; i++)
{
var c = new simpleC();
c.simpleAId = simpleA[i].Name;
c.simpleAName = simpleA[i].Name;
c.simpleBName = simpleB[i].Name;
someList.Add(c);
}

Related

Get count of same value on table

I have this class where the query must result in this list a property.
This property must check on table how many duplicated exists.
This code works, but its very slow. can you help me ?
var lst = _uow.Repository.GetAll();
var query =
from p in lst
select new GetRfqResponse
{
ID = p.ID,
//bad performance
Count = lst.Where(x => x.Property == p.Property).AsQueryable().Count(),
//
};
Counting in a queryable list can be easily achieved using the Count() function:
// Find duplicated names
var byName = from s in studentList
group s by s.StudentName into g
select new { Name = g.Key, Count = g.Count() };
Check this fiddle to see it running.
Below is for InMemory
GroupBy should come to help.
var propertyGroupedList = list.GroupBy(l=>l.Property);
var query = list.Select(l => new GetRfqResponse{
Id = l.Id,
Count = propertyGroupedList.First(g=> g.Key == l.Property).Count()
});
Or you can create a dictionary with key as "Property" and value as count, then you will have to loop just once to store the count.
This allows you to get count in constant time
Dictionary<string, int> map = new Dictionary<string, int>();
foreach (var item in lst)
{
if (!map.ContainsKey(lst.Property))
{
map.Add(item.Property, 1);
}
else
map[item.Property]++;
}
var z = lst.Select(l => new GetRfqResponse{
Id = l.ID,
Count = map[l.Property]
});

Assist with simple count out of a list

I'm trying to count the number of people marked as Break in a column in a list.
currently when I run my foreach loop (the one at the bottom) to count the number of breaks listed it throws the error message cannot convert type char to string. I understand that my class of NewAgent does not contain all string values but I'm only trying to reference one string. I need a way to count the number of times break appears in my list. Break would appear under newAgent.auxreason
public List newAgentList;
List<NewAgent> newAgentList = new List<NewAgent>();
NewAgent newAgents = new ScoreBoardClientTest.NewAgent();
foreach (var item in e.CmsData.Agents)
{
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);
}
int breakCount = 0;
foreach(string s in newAgents.AuxReasons)
{
if (s != null && s.StartsWith("Break")) breakCount++;
}
Try this:
int breakCount = 0;
foreach(var agent in newAgentList)
{
if (!string.IsNullOrEmpty(agent.AuxReasons) && agent.AuxReasons.StartsWith("Break"))
breakCount++;
}
You should also create new object in each iteration:
foreach (var item in e.CmsData.Agents)
{
NewAgent newAgents = new ScoreBoardClientTest.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);
}
First, you need to put NewAgent newAgents = new ScoreBoardClientTest.NewAgent(); inside your first foreach loop because right now you are working with reference to the same object and if you update any property of this object in one place it will be updated for the entire list.
Second, you need to work with newAgentList in the second loop and not with newAgents (this is why you are seeing the exception since you are going trough chars inside the string instead of going trough elements of a list).
This should work:
public List newAgentList;
List<NewAgent> newAgentList = new List<NewAgent>();
foreach (var item in e.CmsData.Agents)
{
NewAgent newAgents = new ScoreBoardClientTest.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);
}
int breakCount = 0;
foreach(string s in newAgentList.AuxReasons)
{
if (!string.IsNullOrWhiteSpace(s.AuxReasons) && s.AuxReasons.StartsWith("Break")) breakCount++;
}
Ok, first of all, we're doing something bad in the loop.
You're declaring newAgents, and setting it over and over again, so it will always have values equal to the last item in e.CmsData.Agents. For example, if you have a list, and the AgNames of the items in the list are:
Bob
Michael
James
newAgents is always going to have an AgentName of "James" when the loop completes, because it's declared out of scope of the loop. Fix this by moving the declaration of your NewAgent placeholder inside the scope of the loop, like following:
List<NewAgent> newAgentList = new List<NewAgent>();
foreach (var item in e.CmsData.Agents)
{
NewAgent newAgents = new ScoreBoardClientTest.NewAgent();
// perform your data transforms
newAgentList.Add(newAgents);
}
That will make it so you're actually adding elements to your list that correspond to the data you're trying to manipulate, and there is no need for that variable to exist outside the loop.
Are you trying to count the number of reasons per Agent in the list, or are you trying to count all "Break" reasons in all agents? The reason I ask is again, you're doing your last foreach loop on your iterator variable, after your iteration process has completed.
To count all elements' breaks, do this instead of your second loop:
int count = newAgentList.Sum(agent =>
agent.AuxReasons.Count(reasons =>
!string.IsNullOrEmpty(reasons) && reasons.StartsWith("Break")));
If you want to count the iterator while you're operating on it, use the inner lambda function in your first loop like this:
foreach (var item in e.CmsData.Agents)
{
// other logic from above
int count = newAgents.AuxReasons.Count(r =>
!string.IsNullOrEmpty(r) && r.StartsWith("Break");
// do something with count before loop ends
}
If you do this latter version, you're going to need to do something with that count before the loop's iteration completes or it will be lost.
If all this is unclear, here's a fully modified version of your code:
List<NewAgent> newAgentList = new List<NewAgent>();
foreach (var item in e.CmsData.Agents)
{
NewAgent newAgents = new ScoreBoardClientTest.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);
}
int breakCount = newAgentList.Count(agent =>
!string.IsNullOrEmpty(agent.AuxReasons) && agent.AuxReasons.StartsWith("Break"));
first move
NewAgent newAgents = new ScoreBoardClientTest.NewAgent();
into the first loop so the newAgents will be new when you add it
second , the second foreach is on a string so it gives you list of chars

How to add calculated fields to SQL list

I am tracking fuel for trucks.
List<FuelLightTruckDataSource> data = new List<FuelLightTruckDataSource>();
using (SystemContext ctx = new SystemContext())
{
List<FuelLightTruckDataSource> dataTransfers
= ctx.FuelTransfer
.Where(tr => DbFunctions.TruncateTime(tr.Date) >= from.Date && DbFunctions.TruncateTime(tr.Date) <= to.Date
//&& tr.ToAsset.AssignedToEmployee.Manager
&& tr.ToAsset.AssignedToEmployee != null
&& tr.ToAsset.AssetType.Code == "L"
&& tr.Volume != null)
//&& (tr.FuelProductType.FuelProductClass.Code == "GAS" || tr.FuelProductType.FuelProductClass.Code == "DSL"))
.GroupBy(tr => new { tr.ToAsset, tr.Date, tr.FuelTankLog.FuelProductType.FuelProductClass, tr.FuelTankLog.FuelCard.FuelVendor,tr.Volume, tr.ToAssetOdometer })
.Select(g => new FuelLightTruckDataSource()
{
Asset = g.FirstOrDefault().ToAsset,
Employee = g.FirstOrDefault().ToAsset.AssignedToEmployee,
ProductClass = g.FirstOrDefault().FuelTankLog.FuelProductType.FuelProductClass,
Vendor = g.FirstOrDefault().FuelTankLog.FuelCard.FuelVendor,
FillSource = FuelFillSource.Transfer,
Source = "Slip Tank",
City = "",
Volume = g.FirstOrDefault().Volume.Value,
Distance = g.FirstOrDefault().ToAssetOdometer,
Date = g.FirstOrDefault().Date,
})
.ToList();
After my query, I need to calculate the consumption rate and distance traveled. "Result" will be a collection of entries including "consumptionRate" and "Distance" and it matches the query above.
// Get consumption rate data for each asset
foreach (int assetId in assetIds)
{
FuelConsumptionRateQueryResult result = FuelConsumptionRateQueryService.Get(assetId, from, to, AssetCounterType.Odometer);
result.Entries.ToList();
}
My question is how do I get the result of my foreach loop and add them to my previous query/list, so they can show up in the report?
Your issue seems to be due to the fact that you're declaring result as a new variable at each iteration of the foreach loop, causing result to be overwritten. Additionally, result isn't in scope once you finish with the foreach loop.
Instead, you probably want something closer to:
var allResults = new List<T>; //where T is whatever object type this is (FuelConsumptionRateQueryResult?)
foreach (int assetId in assetIds)
{
//this is a new variable at each iteration of the loop
FuelConsumptionRateQueryResult result = FuelConsumptionRateQueryService.Get(assetId, from, to, AssetCounterType.Odometer);
allResults.Add(result); //add the current result to your list (outside the scope of this loop)
}
Basically, declare your variable outside of the loop and Add each individual result to the allResults list.
Create the additional data by a LINQ statement and concatenate both lists:
var results = assetIds.Select(id => FuelConsumptionRateQueryService
.Get(id, from, to, AssetCounterType.Odometer);
dataTransfers = dataTransfers.Concat(results);

Not modifying list in loop but its throwing error Collection was modified; enumeration operation may not execute

I am using following code in c# as below.
var result = (from o in db.tblOrderMasters
join od in db.tblOrderDetails on o.order_id equals od.orderdetails_orderId
join c in db.tblCountryMasters on o.order_dCountry equals c.country_id
join cu in db.tblCustomers on o.order_Custid equals cu.cust_id
where o.order_id == orderid && o.order_active == true && o.order_IsDeleted == false && (o.order_status == 2)
select new
{
Code = o.order_code,
Name = o.order_dFirstName + " " + o.order_dLastName,
Quantity = od.Quantity,
[...snip...]
}).ToList();
var Qresult = result;
try
{
foreach (var r in result)
{
if (r.Quantity > 1)
{
for (int i = 1; i < r.Quantity; i++)
{
Qresult.Add(r);
}
}
}
}
Collection was modified; enumeration operation may not execute.
As i read other answers related to this error they are saying that you cant modify a list while iterating it, but as in my code i am not modifying the result list instead i am changing a new list which is Qresult and iterating the main result list so why this error is coming ?
one more thing i want to mention here that when i use quickwatch i can see that in result item also added by this line
Qresult.Add(r);
but i am adding items in Qresult so why item added to result
You write :
var Qresult = result;
foreach (var r in result) //result is Qresult
{
..
Qresult.Add(r);
}
it's the same like write:
foreach (var r in Qresult)
{
..
Qresult.Add(r);
}
So you change actually collection
So you want to clone this anonymous type Quantity-times. Since you cannot modify the collection in the foreach (and assigning the list to a different variable doesn't create a copy), you could use this Linq:
var Qresult = result
.SelectMany(o => Enumerable.Range(1, o.Quantity)
.Select(i => new {
Code = o.order_code,
Name = o.order_dFirstName + " " + o.order_dLastName,
Quantity = od.Quantity,
[...snip...]
}
)).ToList();
Qresult is the same object as result - both references the same object in memory. Do not use anonymous object and create new List, as
var Qresult = new List<YourObject>();
try
{
foreach (var r in result)
{
if (r.Quantity > 1)
{
for (int i = 1; i < r.Quantity; i++)
{
Qresult.Add(r);
}
}
}
}

Comparing two large generic lists

I cannot find a specific example of this, so am posting the question. Any help appreciated.
I have two large generic lists, both with over 300K items.
I am looping through the first list to pull back information and generate a new item for a new list on the fly, but I need to search within the second list and return a value, based on THREE matching criteria, if found to add to the list, however as you can imagine, doing this 300k * 300k times is taking time.
Is there any way I can do this more efficiently?
My code:
var reportList = new List<StocksHeldInCustody>();
foreach (var correctDepotHolding in correctDepotHoldings)
{
var reportLine = new StocksHeldInCustody();
reportLine.ClientNo = correctDepotHolding.ClientNo;
reportLine.Value = correctDepotHolding.ValueOfStock;
reportLine.Depot = correctDepotHolding.Depot;
reportLine.SEDOL = correctDepotHolding.StockCode;
reportLine.Units = correctDepotHolding.QuantityHeld;
reportLine.Custodian = "Unknown";
reportLine.StockName = correctDepotHolding.StockR1.Trim() + " " + correctDepotHolding.StockR2.Trim();
//Get custodian info
foreach (var ccHolding in ccHoldList)
{
if (correctDepotHolding.ClientNo != ccHolding.ClientNo) continue;
if (correctDepotHolding.Depot != ccHolding.Depot) continue;
if (correctDepotHolding.StockCode != ccHolding.StockCode) continue;
if (correctDepotHolding.QuantityHeld != ccHolding.QuantityHeld) continue;
reportLine.Custodian = ccHolding.Custodian;
break;
}
reportList.Add(reportLine);
}
As Pranay says, a join is probably what you want:
var query = from correct in correctDepotHoldings
join ccHolding in ccHoldList
on new { correct.ClientNo, correct.Depot,
correct.StockCode, correct.QuantityHeld }
equals new { ccHolding.ClientNo, ccHolding.Depot,
ccHolding.StockCode, ccHolding.QuantityHeld }
// TODO: Fill in the properties here based on correct and ccHolding
select new StocksHeldInCustody { ... };
var reportList = query.ToList();
You could move the data from the lookup list into a dictionary, with the key being a unique hash of the 3 items you are searching on. Then you will have very quick lookups and save millions of iterations.
Check my full post : Linq Join on Mutiple columns using Anonymous type
Make use of Linq inner join that will do work for you.
var list = ( from x in entity
join y in entity2
on new { x.field1, x.field2 }
equals new { y.field1, y.field2 }
select new entity { fields to select}).ToList();
Join of linq on multiple field
EmployeeDataContext edb= new EmployeeDataContext();
var cust = from c in edb.Customers
join d in edb.Distributors on
new { CityID = c.CityId, StateID = c.StateId, CountryID = c.CountryId,
Id = c.DistributorId }
equals
new { CityID = d.CityId, StateID = d.StateId, CountryID = d.CountryId,
Id = d.DistributorId }
select c;
Use LINQ to join the lists and return it how you like.
eg
var list1 = GetMassiveList();
var list2 = GetMassiveList();
var list3 = from a in list1
join b in list2
on new { a.Prop1, a.Prop2 } equals
new { b.Prop1, b.Prop2 }
select new { a.Prop1, b.Prop2 };
To do your outter join, you can use DefaultIfEmpty()
This example is setting your RIGHT part of the join to a default object (often null) for the cases where a join wasn't made.
eg
from a in list1
join b in list2
on new { a.Prop1, a.Prop2 } equals
new { b.Prop1, b.Prop2 }
into outer
from b in outer.DefaultIfEmpty()
select new
Prop1 = a.Prop1,
Prop2 = b != null ? b.Prop2 : "Value for Prop2 if the b join is null"
}

Categories

Resources