Converting two loops to single LINQ query using C# - c#

I searched some links here to change nested loops to single Linq, I tried using those, part of code is not working, I need some expert guidance to fix this,
UPDATE 1:
I guess wasn't clear in my explanation, the loops works fine! as expected, I am getting correct results, but I am doing optimization, instead of using two loops i need the same code to be converted to single linq.
here is the code :
foreach (var ob in all_request_list.Where(x => x.StartDate != x.EndDate)) {
int consq_dates = ob.EndDate.DateDiff(ob.StartDate);
for (int i = 0; i <= consq_dates; i++) {
combined_list.Add(new { ShiftID = ob.ShiftID, SkillID = ob.SkillID, EmployeeID = ob.EmployeeID, AssignDate = ob.StartDate.AddDays(i), ProfileID = ob.ProfileID });
}
}
I have problem adding increment variable i to ob.StartDate.AddDays(i).
any help will be appreciated.

Is this what you're looking for?
var items = from ob in all_request_list
where ob.StartDate != ob.EndDate
let consq_dates = ob.EndDate.DateDiff(ob.StartDate)
from i in Enumerable.Range(0, consq_dates + 1)
select new { ShiftID = ob.ShiftID, SkillID = ob.SkillID, EmployeeID = ob.EmployeeID, AssignDate = ob.StartDate.AddDays(i), ProfileID = ob.ProfileID };
combined_list.AddRange(items);
But: You've code that works. You understand that code. Why do you wan't to change that? BTW: Your two loops will be faster than that linq.

You can use the following Linq:
var items = all_request_list
.Where(x => x.StartDate != x.EndDate)
.SelectMany(x => Enumerable.Range(0, x.EndDate.DateDiff(x.StartDate) + 1)
.Select(y => new { ShiftID = x.ShiftID, SkillID = x.SkillID, EmployeeID = x.EmployeeID, AssignDate = x.StartDate.AddDays(y), ProfileID = x.ProfileID }))
combined_list.AddRange(items);
What it does exactly is Creating an IEumerable<> of results for each item in the all_request_list.Where using Enumerable.Range (This is the part which replaces your for loop), than flattens it using the SelectMany method.
It might be better than a for loop in the terms of readability/maintainability but keep in mind that Linq usually slower than plain loops (tl;dr: Understand what Linq does internally and what it will do in your case).

I don't know exactly what error you're getting, but it could be due to the fact that certain functions cannot be executed inside a linq statement, since it internally translates it to sql. Try this:
foreach (var ob in all_request_list.Where(x => x.StartDate != x.EndDate))
{
int consq_dates = ob.EndDate.DateDiff(ob.StartDate);
for (int i = 0; i <= consq_dates; i++)
{
var newDate = ob.StartDate.AddDays(i);
combined_list.Add(new { ShiftID = ob.ShiftID, SkillID = ob.SkillID, EmployeeID = ob.EmployeeID, AssignDate = newDate , ProfileID = ob.ProfileID });
}
}
If it still gives you an error, could you specify what error you're receiving, such as the name, type, etc.

Related

How can I fill the gaps in attribute values in the list?

I have a list:
List<BtnCountViews> btnCountViewsList;
The BtnCountViews class looks like this:
public class BtnCountViews
{
public int DayOfYear { get; set; }
public int BtnCount { get; set; }
public int Views { get; set; }
}
I have a rather unusual requirement and I am not sure how to go about starting to implement it.
What I would like to do is to fill in the btnCountViewsList with `BtnCountViews for the missing DayOfYear with objects that have a BtnCount of 0 and Views of 0.
To give me a start can anyone tell me how I can find the min and max DayOfYear in the btnCountViewsList. Note I tagged this with LINQ but I'm not sure if this is the best tool to use.
Also would be happy if someone can suggest a way to fill in the missing objects but that's not really the focus of this question as I think I need to find out how to get the min and max first.
You can add missing days without finding min and max explicitly:
Sort the list by DayOfYear in ascending order (how?)
Start a loop index i at the end of the list, and work your way backward; stop when i reaches zero
Compare DayOfYear attribute at i and i-1
If the two days differ by one, move down to the next i
Otherwise insert a new record with DayOfYear set to that of btnCountViewsList[i] minus one.
At the end of this process your list would contain entries for each value of DayOfYear. Here is a sample implementation:
items.Sort((x, y) => x.DayOfYear.CompareTo(y.DayOfYear));
Console.WriteLine("Before: {0}", string.Join(", ", items.Select(x => x.DayOfYear)));
int i = items.Count-1;
while (i > 0) {
if (items[i].DayOfYear == items[i-1].DayOfYear+1) {
i--;
} else {
items.Insert(i, new BtnCountViews { DayOfYear = items[i].DayOfYear-1 });
}
}
Demo.
This is working on linqpad:
Int32 max = 0, min = 0;
btnCountViewsList.ForEach(x => {
min = Math.Min(x.Views, min);
max = Math.Max(x.Views, max);
});
What I would like to do is to fill in the btnCountViewsList with `BtnCountViews for the missing DayOfYear with objects that have a BtnCount of 0 and Views of 0.
My suggestion is that we don't try to find the missing days, we create all:
BtnCountViews[] arr = new BtnCountViews[365]; // or 366?
// suppose DayOfYear begins with 0.
for (int i = 0; i < arr.Length; i++)
{
arr[i] = new BtnCountViews { DayOfYear = i };
}
foreach (BtnCountViews item in btnCountViewsList)
{
arr[item.DayOfYear].BtnCount = item.BtnCount;
arr[item.DayOfYear].Views = item.Views;
}
then arr is what you want.
And if the result should be the btnCountViewsList:
btnCountViewsList.Clear();
btnCountViewsList.AddRange(arr);
So the lazy in me says, make a backfill list and use your existing (and gappy) list as a map.
public static IList<BtnCountViews> GetDefaultList()
{
var defaultList = Enumerable.Range(1, 365).Select(e =>
new BtnCountViews
{
DayOfYear = e,
BtnCount = 0,
Views = 0
}
).ToList();
return defaultList;
}
Iterate through the backfill list and consult the map to see if the DayOfYear value exists as a key, and if not, then add it to the map.
public static IList<BtnCountViews> GetBackFilledList(IList<BtnCountViews> incoming)
{
var map = incoming.ToDictionary(k => k.DayOfYear, v => v);
var defaultList = GetDefaultList();
foreach(var itm in defaultList)
{
if (map.ContainsKey(itm.DayOfYear)) continue;
map.Add(itm.DayOfYear, itm);
}
return map.Select(m => m.Value).ToList();
}
Once the iteration is finished, convert the map into a list, which should now consist of the original values + default values for missing DayOfYear entries as well.
return map.Select(m => m.Value).ToList();
Dotnetfiddle of a sample program here: https://dotnetfiddle.net/wSJy56
Is there a more elegant way to do this? Most surely. But this code executes in about 0.011 seconds, which to me is pretty decent so long as you're not calling this functionality over and over again (e.g. you decide to analyze 30 years of data and need to get that done in 0.011 seconds). But then we'd have to be looking more towards parallelism rather than code elegance to solve that can of worms.
Hope this helps...
Try the following
btnCountViewsList = btnCountViewsList.Where(b => b.BtnCount == 0).Where(v => v.Views == 0).ToList();
If I understood what you were asking, you want to get objects where BtnCount = 0 and Views = 0.
This will select all the objects where Views = 0, and then that IEnumarable will be through another LINQ expression where it only selects the other property that equals to 0.
The shortest linq way, using an left outer join (LEFT OUTER JOIN in LINQ) and Range
var result = (from a in Enumerable.Range(0, 365)
join lst in btnCountViewsList on a equals lst.DayOfYear into ps
from p in ps.DefaultIfEmpty()
select (p==null) ? new BtnCountViews() { DayOfYear = a}:p).ToList()
among the lines of some other responses, but without hard coding the total days of the year as leap years will have 366 days
var range = new
{
Start = new DateTime(2017, 1, 1),
End = new DateTime(2017, 12, 31),
};
var days = Enumerable.Range(range.Start.DayOfYear, range.End.DayOfYear);
var query = from day in days
from counter in
(
from temp in btnCountViewsList
where temp.DayOfYear == day
select temp
).DefaultIfEmpty()
select new BtnCountViews
{
DayOfYear = day,
BtnCount = counter == null ? 0 : counter.BtnCount,
Views = counter == null ? 0 : counter.Views,
};
will give you something like

avoid loop to generate nullable collection, increase performance

I've been using Stopwatch and it looks like the below query is very expensive in terms of performance, even though what I already have below I find most optimal based on various reading (change foreach loop with for, use arrays instead of collection, using anonymous type not to take the whole table from DB). Is there a way to make it faster? I need to fill the prices array, which needs to be nullable. I'm not sure if I'm missing something?
public float?[] getPricesOfGivenProducts(string[] lookupProducts)
{
var idsAndPrices = from r in myReadings select
new { ProductId = r.ProductId, Price = r.Price };
float?[] prices = new float?[lookupProducts.Length];
for(int i=0;i<lookupProducts.Length;i++)
{
string id = lookupProducts[i];
if (idsAndPrices.Any(r => r.ProductId == id))
{
prices[i] = idsAndPrices.Where(p => p.ProductId == id)
.Select(a=>a.Price).FirstOrDefault();
}
else
{
prices[i] = null;
}
}
return prices;
}
It's likely every time you call idsAndPrices.Any(r => r.ProductId == id), you are hitting the database, because you haven't materialized the result (.ToList() would somewhat fix it). That's probably the main cause of the bad performance. However, simply loading it all into memory still means you're searching the list for a productID every time (twice per product, in fact).
Use a Dictionary when you're trying to do lookups.
public float?[] getPricesOfGivenProducts(string[] lookupProducts)
{
var idsAndPrices = myReadings.ToDictionary(r => r.ProductId, r => r.Price);
float?[] prices = new float?[lookupProducts.Length];
for (int i = 0; i < lookupProducts.Length; i++)
{
string id = lookupProducts[i];
if (idsAndPrices.ContainsKey(id))
{
prices[i] = idsAndPrices[id];
}
else
{
prices[i] = null;
}
}
return prices;
}
To improve this further, we can identify that we only care about products passed to us in the array. So let's not load the entire database:
var idsAndPrices = myReadings
.Where(r => lookupProducts.Contains(r.ProductId))
.ToDictionary(r => r.ProductId, r => r.Price);
Now, we might want to avoid the 'return null price if we can't find the product' scenario. Perhaps the validity of the product id should be handled elsewhere. In that case, we can make the method a lot simpler (and we won't have to rely on having the array in order, either):
public Dictionary<string, float> getPricesOfGivenProducts(string[] lookupProducts)
{
return myReadings
.Where(r => lookupProducts.Contains(r.ProductId))
.ToDictionary(r => r.ProductId, r => r.Price);
}
And a note unrelated to performance, you should use decimal for money
Assuming that idsAndPrices is an IEnumerable<T>, you should make it's initialization:
var idsAndPrices = (from r in myReadings select
new { ProductId = r.ProductId, Price = r.Price })
.ToList();
It's likely that the calls to:
idsAndPrices.Any(r => r.ProductId == id)
and:
idsAndPrices.Where(p => p.ProductId == id)
..are causing the IEnumerable<T> to be evaluated every time it's called.
Based on
using anonymous type not to take the whole table from DB
I assume myReadings is the database table and
var idsAndPrices =
from r in myReadings
select new { ProductId = r.ProductId, Price = r.Price };
is the database query.
Your implementation is far from optimal (I would rather say quite inefficient) because the above query is executed twice per each element of lookupProducts array - idsAndPrices.Any(...) and idsAndPrices.Where(...) statements.
The optimal way I see is to filter as much as possible the database query, and then use the most efficient LINQ to Objects method for correlating two in memory sequences - join, in your case left outer join:
var dbQuery =
from r in myReadings
where lookupProducts.Contains(r.ProductId)
select new { ProductId = r.ProductId, Price = r.Price };
var query =
from p in lookupProducts
join r in dbQuery on p equals r.ProductId into rGroup
from r in rGroup.DefaultIfEmpty().Take(1)
select r?.Price;
var result = query.ToArray();
The Any and FirstOrDefault are O(n) and redundant. You can get a 50% speed up just by removing theAll call. FirstOrDefault will give you back a null, so use it to get a product object (remove the Select). If you want to really speed it up you should just loop through the products and check if prices[p.ProductId] != null before setting prices[p.ProductId] = p.Price.
bit of extra code code there
var idsAndPrices = (from r in myReadings select
new { ProductId = r.ProductId, Price = r.Price })
.ToList();
for(int i=0;i<lookupProducts.Length;i++)
{
string id = lookupProducts[i];
prices[i] = idsAndPrices.FirstOrDefault(p => p.ProductId == id);
}
better yet
Dictionary<Int, Float?> dp = new Dictionary<Int, Float?>();
foreach(var reading in myReadings)
dp.add(r.ProductId, r.Price);
for(int i=0;i<lookupProducts.Length;i++)
{
string id = lookupProducts[i];
if(dp.Contains(id)
prices[i] = dp[id];
else
prices[i] = null;
}

c# linq syntax slow due to multiple queries in single query

I am wondering if there is a better, more efficient way to re-code the linq syntax below to make the query run faster i.e. with a single call to the database. My database is located remotely which causes this to be quite slow:
var query = (from ticket in dataClassesDataContext.Tickets.Where(TicketsToShow.And(SearchVals))
select new
{
Priority = ticket.TicketPriority.TicketPriorityName,
Ticket = string.Format(TicketFormat, ticket.TicketID),
AssetId = ticket.Asset.Serial,
OpenDate = ticket.CheckedInDate,
OpenFor = CalculateOpenDaysAndHours(ticket.CheckedInDate, ticket.ClosedDate),
Account = ticket.Account.Customer.Name,
Description = ticket.Description.Replace("\n", ", "),
Status = ticket.TicketStatus.TicketStatusName,
Closed = ticket.ClosedDate,
THIS IS THE CAUSE ====>>> Amount = GetOutstandingBalanceForTicket(ticket.TicketID),
Paid = ticket.Paid,
Warranty = ticket.WarrantyRepair,
AssetLocation = GetAssetLocationNameFromID(ticket.Asset.LocationID, AssLocNames)
}).Skip(totalToDisplay * page).Take(totalToDisplay);
if (SortOrder.ToLower().Contains("Asc".ToLower()))
{
query = query.OrderBy(p => p.OpenDate);
}
else
{
query = query.OrderByDescending(p => p.OpenDate);
}//ENDIF
The main cause for the poor performance is the code in the function GetOutstandingBalanceForTicket below which calculates the sum of all items in an invoice and returns this as a total in a string:
public static string GetOutstandingBalanceForTicket(int TicketID)
{
string result = string.Empty;
decimal total = 0;
try
{
using (DataClassesDataContext dataClassesDataContext = new DataClassesDataContext(cDbConnection.GetConnectionString()))
{
var queryCustomerTickets = from ticket in dataClassesDataContext.Tickets
where
(ticket.TicketID == TicketID)
select ticket;
if (queryCustomerTickets != null)
{
foreach (var ticket in queryCustomerTickets)
{
var queryTicketChargeItems = from chargeItem in dataClassesDataContext.ProductChargeItems
where chargeItem.ChargeID == ticket.ChargeID &&
chargeItem.Deleted == null
select chargeItem;
foreach (var chargeItem in queryTicketChargeItems)
{
total += (chargeItem.Qty * chargeItem.Price);
}
}
}
}
}
catch (Exception ex)
{
}
return total.ToString("0.##");
}
Thank you in advance.
As you pointed out this code is quite slow as a query will be required for each ticket.
to eliminate the need for multiple queries you should look at applying an inner join between the ticketsToShow and the tickets entity (on the ticketid), using groupby to provide the sum of the charges for each ticket.
This is well illustrated in the answers to LINQ: Using INNER JOIN, Group and SUM
Ideally you would probably approach it more as an eager loading all at once type of setup. However, I do not think linq2sql supports that (I know EF does). One thing you can do is avoid the nested query though. Since you already have access to the ticket table, perhaps you should just issue a Sum() on it from your select statement. Hard for me to verify if any of this is an improvement so this code is kind of on the fly if you will.
//(from ticket in dataClassesDataContext.Tickets.Where(TicketsToShow.And(SearchVals))
(from ticket in dataClassesDataContext.Tickets
//this would be where you could eager load if possible (not entirely required)
//.Include is an EF method used only as example
/*.Include(t => t.TicketPriority)//eager load required entities
.Include(t => t.Asset)//eager load required entities
.Include(t => t.Account.Customer)//eager load required entities
.Include(t => t.TicketStatus)//eager load required entities
.Include(t => t.ProductChargeItems)//eager load required entities
*/
.Where(TicketsToShow.And(SearchVals))
select new
{
Priority = ticket.TicketPriority.TicketPriorityName,
Ticket = string.Format(TicketFormat, ticket.TicketID),
AssetId = ticket.Asset.Serial,
OpenDate = ticket.CheckedInDate,
OpenFor = CalculateOpenDaysAndHours(ticket.CheckedInDate, ticket.ClosedDate),
Account = ticket.Account.Customer.Name,
Description = ticket.Description.Replace("\n", ", "),
Status = ticket.TicketStatus.TicketStatusName,
Closed = ticket.ClosedDate,
//Use Sum and the foreign relation instead of a nested query
Amount = ticket.ProductChargeItems.Where(pci => pci.Deleted == null).Sum(pci => pci.Qty * pci.Price),
Paid = ticket.Paid,
Warranty = ticket.WarrantyRepair,
AssetLocation = GetAssetLocationNameFromID(ticket.Asset.LocationID, AssLocNames)
}).Skip(totalToDisplay * page).Take(totalToDisplay);
if (SortOrder.ToLower().Contains("Asc".ToLower()))
{
query = query.OrderBy(p => p.OpenDate);
}
else
{
query = query.OrderByDescending(p => p.OpenDate);
}
I think, you can make this query simplier. Somethink like this:
public static string GetOutstandingBalanceForTicket(DataClassesDataContext context, int TicketID)
{
decimal total = 0;
var total = (from ticket in context.Tickets
join chargeItem from context.ProductChargeItems on chargeItem.ChargeID == ticket.ChargeID
where (ticket.TicketID == TicketID && chargeItem.Deleted == null)
select chargeItem).Sum(chargeItem => chargeItem.Qty * chargeItem.Price);
return total.ToString("0.##");
}
/*...*/
Amount = GetOutstandingBalanceForTicket(dataClassesDataContext, ticket.TicketID),
Now, you can inline this methos in your query.
It can contains syntax errors, because I wrote it in notepad.

C# List grouping and assigning a value

I have a list of Orders. This list contains multiple orders for the same item, see the table below.
I then want to assign each item that is the same (i.e. ABC) the same block ID. So ABC would have a block ID of 1 & each GHJ would have a block ID of 2 etc. What is the best way of doing this?
Currently I order the list by Order ID and then have a for loop and check if the current Order ID is equal to the next Order ID if so assign the two the same block ID. Is there a better way of doing this using linq or any other approach?
Order ID Block ID
ABC
ABC
ABC
GHJ
GHJ
GHJ
MNO
MNO
You can do this that way, it will assign same blockid for same orderid
var ordered = listOrder.GroupBy(x => x.OrderId).ToList();
for (int i = 0; i < ordered.Count(); i++)
{
ordered[i].ForEach(x=>x.BlockId=i+1);
}
it will group orders by orderid then assign each group next blockid. Note that it won't be done fully in linq, because linq is for querying not changing data.
Always depends of what better means for you in this context.
There are a bunch of possible solutions to this trivial problem.
On top of my head, I could think of:
var blockId = 1;
foreach(var grp in yourOrders.GroupBy(o => o.OrderId))
{
foreach(var order in grp)
{
order.BlockId = blockId;
}
blockId++;
}
or (be more "linqy"):
foreach(var t in yourOrders.GroupBy(o => o.OrderId).Zip(Enumerable.Range(1, Int32.MaxValue), (grp, bid) => new {grp, bid}))
{
foreach(var order in t.grp)
{
order.BlockId = t.bid;
}
}
or (can you still follow the code?):
var orders = yourOrders.GroupBy(o => o.OrderId)
.Zip(Enumerable.Range(1, Int16.MaxValue), (grp, id) => new {orders = grp, id})
.SelectMany(grp => grp.orders, (grp, order) => new {order, grp.id});
foreach(var item in orders)
{
item.order.BlockId = item.id;
}
or (probably the closest to a simple for loop):
Order prev = null;
blockId = 1;
foreach (var order in yourOrders.OrderBy(o => o.OrderId))
{
order.BlockId = (prev == null || prev.OrderId == order.OrderId) ?
blockId :
++blockId;
prev = order;
}
Linq? Yes.
Better than a simple loop? Uhmmmm....
Using Linq will not magically make your code better. Surely, it can make it often more declarative/readable/faster (in terms of lazy evaluation), but sure enough you can make otherwise fine imperative loops unreadable if you try to force the use of Linq just because Linq.
As a side note:
if you want to have feedback on working code, you can ask at codereview.stackexchange.com

Looping through IEnumerable using ForEach in C#

I'm trying to loop through an IEnumerable but for some reason it is not going through the foreach loop, as the values for each ChartClass was not modified.
public IEnumerable<ChartClass> get(string ID, string buildname, string placeholder)
{
var context = new Entities();
var metrics = from c in context.VSTS_CODE_METRICS
where c.BUILD_NAME == buildname && c.OBJECT_TYPE == "Namespace"
group c by c.BUILD_ID into g
select new ChartClass
{
Build_ID = g.Key,
BuildTrim = g.Key,
Index = g.Average(c => c.MAINTAINABILITYINDEX_).Value
};
foreach (var i in metrics)
{
int num = i.BuildTrim.LastIndexOf('_');
i.BuildTrim = "2";
}
return metrics;
}
I'm trying to change each ChartClass' BuildTrim field to "2" but it's not happening for some reason
Why don't you just set the BuildTrim equal to "2" in the query?
public IEnumerable<ChartClass> get(string ID, string buildname, string placeholder)
{
var context = new Entities();
var metrics = from c in context.VSTS_CODE_METRICS
where c.BUILD_NAME == buildname && c.OBJECT_TYPE == "Namespace"
group c by c.BUILD_ID into g
select new ChartClass
{
Build_ID = g.Key,
BuildTrim = "2",//g.Key,
Index = g.Average(c => c.MAINTAINABILITYINDEX_).Value
};
/*foreach (var i in metrics)
{
int num = i.BuildTrim.LastIndexOf('_');
i.BuildTrim = "2";
}*/
return metrics;
}
metrics is an IQueryable. Every single time you iterate the object it is going to to to the database, query the items that you're asking for, put them into the objects that you specified, and then allow those objects to be iterated. You're modifying the objects that are being returned, but those same in-memory objects won't be used if you iterate the sequence again. Instead it's going back to the database a second time and pulling back a fresh query that doesn't have your changes.
As mentioned in the other answer, if you simply modify your first Select call to set BuildTrim to the desired value to begin with, rather than modifying an object that is just about to be thrown away, your query will work as intended.

Categories

Resources