Calculating totals in linq query - c#

I have a Model called JobReport which looks like this (simplified)
public class JobReport
{
public JobReport()
{
WorkOrders = new List<WorkOrder>();
}
public int JobID { get; set; }
public decimal WorkOrderTotal {get; set; }
public List<WorkOrder> WorkOrders{ get; set; }
}
public class WorkOrder
{
public WorkOrder()
{
Total = 0;
}
public string Trade { get; set; }
public int WorkOrderID { get; set; }
public decimal? Total { get; set; }
}
I now have a Linq query which gets me all the Jobs that have WorkOrders that have a trade which is in a passed array thanks to Linq Query where related entity contains value from array:
jobs = jobs
.Where(x => x.WorkOrders.Any(y => trades.Contains(y.Trade)));
How do I now get the WorkOrderTotal, which is the sum of the Total in the workorders that meet the predicate of the above query? I can't see how to add .Sum() anywhere?
EDIT
Just to confirm, each job needs the sum of it's workorders that are in the given trades.

Perhaps a slightly easier solution to those already posted would be to add a property to your JobReport called WorkOrderValue:
public decimal? WorkOrdersValue { get; set; }
Now you can query on the jobs that meet your criteria:
jobs = jobs
.Where(x => x.WorkOrders
.Any(y => trades.Contains(y.Trade.ToLower())))
.ToList();
And separately calculate the total for each job:
foreach (var job in jobs)
{
job.WorkOrdersValue = job.WorkOrders.Where
(y => trades.Contains(y.Trade.ToLower())).Sum(wo => wo.Total);
}

Try something like this:
IEnumerable<decimal> workOrderTotals = jobs
.Where(x => x.WorkOrders.Any(y => trades.Contains(y.Trade)))
.Select( j => j.WorkOrders.Sum(wo => wo.Total ?? 0));
And here's a test case :
var jobs = new List<JobReport>();
jobs.Add(new JobReport{ WorkOrders = new List<WorkOrder>{ new WorkOrder{ Total = 10} }});
jobs.Add(new JobReport { WorkOrders = new List<WorkOrder> { new WorkOrder { Total = 10 }, new WorkOrder { Total = 10 } } });
The result is an enumerable containing 2 values 10 , 20

Considering this as the test data
JobReport job1 = new JobReport();
job1.JobID = 1;
job1.WorkOrders.Add(new WorkOrder() { WorkOrderID = 2, Trade = "trade1", Total = 10});
job1.WorkOrders.Add(new WorkOrder() { WorkOrderID = 3, Trade = "trade2", Total = 20 });
job1.WorkOrders.Add(new WorkOrder() { WorkOrderID = 4, Trade = "trade1", Total = 25 });
JobReport job2 = new JobReport();
job2.JobID = 2;
job2.WorkOrders.Add(new WorkOrder() { WorkOrderID = 1, Trade = "trade1", Total = 10 });
job2.WorkOrders.Add(new WorkOrder() { WorkOrderID = 5, Trade = "trade2", Total = 20 });
job2.WorkOrders.Add(new WorkOrder() { WorkOrderID = 6, Trade = "trade2", Total = 30 });
job2.WorkOrders.Add(new WorkOrder() { WorkOrderID = 7, Trade = "trade3", Total = 10 });
List<JobReport> jobs = new List<JobReport>();
jobs.Add(job1);
jobs.Add(job2);
You could do something like this.
var groupedJobs = jobs.GroupBy(a => a.JobID)
.Select(b => new { JobId = b.Key, WorkOrdersByTrade = b.Select(c => c.WorkOrders.GroupBy(d => d.Trade)
.Select(g => new { Trade = g.Key, tradeSum = g.Sum(s => s.Total) })) });
Further by defining the following classes
public class TradeTotal
{
public string Trade { get; set; }
public decimal? Total { get; set; }
}
public class JobTrade
{
public int JobId { get; set; }
public List<TradeTotal> TradeTotals { get; set; }
}
You can get the results in the format that you wanted
var JobTradeList = groupedJobs.Select(x => new JobTrade() { JobId = x.JobId, TradeTotals = x.WorkOrdersByTrade.SelectMany(s => s.Select(v => new TradeTotal() { Total = v.tradeSum, Trade = v.Trade })).ToList() }).ToList();
Code may be not 100% clean; but I think this is what you are after.

Related

number of child with max parent id with linq and c#

Is there any way i can get number of child with max parent id and list the result with linq?
I'm trying to bring the total of values ​​by Status, but i can only get the last one from the child
what I have done so far:
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var lstRfq = new List<RfqEvents>()
{
new RfqEvents(1,1,DateTime.Parse("2021-05-06 03:00:00+00"),1),
new RfqEvents(2,2,DateTime.Parse("2021-05-06 03:00:00+00"),1),
new RfqEvents(3,2,DateTime.Parse("2021-05-06 03:00:00+00"),1),
new RfqEvents(4,3,DateTime.Parse("2021-05-06 00:00:00+00"),2),
new RfqEvents(5,4,DateTime.Parse("2021-05-06 00:00:00+00"),2),
new RfqEvents(6,5,DateTime.Parse("2021-05-06 00:00:00+00"),2),
new RfqEvents(7,5,DateTime.Parse("2021-05-06 00:00:00+00"),2),
new RfqEvents(8,5,DateTime.Parse("2021-05-06 00:00:00+00"),3),
new RfqEvents(9,6,DateTime.Parse("2021-05-06 00:00:00+00"),3),
new RfqEvents(10,6,DateTime.Parse("2021-05-06 00:00:00+00"),3),
};
var subquery = from c in lstRfq
group c by c.RfqId into g
select new InternalStatusInformations
{
RfqId = g.Key,
RfqEventId = g.Max(a => a.Id),
StatusId = g.Select(p => p.Status).FirstOrDefault()
};
var sss = from d in lstRfq.Where(p=> subquery.Select(p=>p.RfqEventId).Contains(p.Id))
group d by d.Status into z
select new InternalStatusInformations
{
StatusId = z.Key,
Total = z.Count(),
Past = z.Where(p => p.DueDate.HasValue && p.DueDate.Value.Date < DateTime.Now.Date).Count(),
Future = z.Where(p => p.DueDate.HasValue && p.DueDate.Value.Date > DateTime.Now.Date).Count(),
Today = z.Where(p => p.DueDate.HasValue && p.DueDate.Value.Date == DateTime.Now.Date).Count(),
FiveDays = z.Where(p => (p.DueDate.HasValue && p.DueDate.Value.Date > DateTime.Now.Date) && p.DueDate.HasValue && p.DueDate.Value.Date < DateTime.Now.Date.AddDays(5)).Count(),
};
//expected: Status 1: 2 values
// Status 2: 3 values
// Status 3: 2 value
//output: Status 1: 2 values
// Status 2: 2 values
// Status 3: 2 values
sss.Dump();
}
public class InternalStatusInformations
{
public int RfqEventId { get; set; }
public int RfqId { get; set; }
public int StatusId { get; set; }
public int Future { get; set; }
public int Past { get; set; }
public int Today { get; set; }
public int FiveDays { get; set; }
public int Total { get; set; }
public DateTime? DueDate { get; set; }
}
public class RfqEvents
{
public RfqEvents(int id, int rfqId, DateTime? dueDate, int status)
{
Id = id;
RfqId = rfqId;
DueDate = dueDate;
Status = status;
}
public int Id { get; set; }
public DateTime? DueDate { get; set; }
public int RfqId { get; set; }
public int Status { get; set; }
}
}
https://dotnetfiddle.net/YoRsIG
but something is not right with the results, could you guys help me?
If you just want to count the number of distinct RfqId values in each status, this should do it:
var pairs = lstRfq
.GroupBy(evt => evt.Status)
.Select(grouping =>
{
var status = grouping.Key;
var count = grouping
.Select(evt => evt.RfqId)
.Distinct()
.Count();
return (status, count);
});
foreach ((var status, var count) in pairs)
{
Console.WriteLine($"Status {status}: {count} values");
}
Output is:
Status 1: 2 values
Status 2: 3 values
Status 3: 2 values

Count elements of an array only using Linq?

I have the 3 classes Item, Order and Management.
Order has an array with ordered Items, Management has a List of different Orders. How can I display the number of ordered items for all PurchaseOrders with the given name?
For example: Item1 is ordered 2 times with quantity = 5 and 2 times with quantity = 7, so the total number is 2*5+2*7=24.
I can solve the task, but only Linq should be used without loops, etc.
class MainClass
{
public static void Main(string[] args)
{
Management Management = new Management();
Management.PrintQuantityForSingleItem("Item1");
}
}
class Item
{
public string Name { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
public Item(string Name, decimal Price, int Quantity)
{
this.Name = Name;
this.Price = Price;
this.Quantity = Quantity;
}
}
class Order
{
public int Id { get; set; }
public Item[] Items { get; set; }
public Order(int Id, Item[] Items)
{
this.Id = Id;
this.Items = Items;
}
}
class Management
{
public List<Order> Orders { get; set; }
public Management()
{
Item i1 = new Item("Item1", 2.0M, 5);
Item i2 = new Item("Item2", 3.0M, 6);
Item i3 = new Item("Item1", 2.0M, 7);
Orders = new List<Order>()
{
new Order(1, new Item[]{i1, i2}),
new Order(2, new Item[]{i3}),
new Order(3, new Item[]{i1, i3}),
};
}
//displays the total number of ordered items for all Orders with the given name on the console.
public void PrintQuantityForSingleItem(string itemName)
{
var result = (from x in Orders
select x.Items).ToList();
int counter = 0;
for (int i = 0; i < result.Count(); i++)
{
for (int a = 0; a < result[i].Count(); a++)
{
if (result[i][a].Name == itemName)
{
counter = counter + result[i][a].Quantity;
}
Console.WriteLine(result[i][a].Name);
}
}
Console.WriteLine(itemName + " " + counter);//for example: shows 24 for item1
}
}
You'll need to select all order items, group them by name and then calculate the sum value for the Quantity. The final step is to get the calculated sum value by item name and display it
public void PrintQuantityForSingleItem(string itemName)
{
var results = Orders
.SelectMany(o => o.Items)
.GroupBy(i => i.Name)
.Select(g => new { Item = g.Key, Sum = g.Sum(i => i.Quantity) });
var item = results.FirstOrDefault(r => r.Item.Equals(itemName, StringComparison.OrdinalIgnoreCase));
Console.WriteLine(itemName + " " + item?.Sum);//shows 24 for item1
}
Welcome to SO. It is obviously not advisable to look up items by name, but ignoring aspects of the design, here is one way of doing it:
var result = Orders.SelectMany(i => i.Items)
.Where(i => i.Name == itemName)
.Sum(i => i.Quantity);
Console.WriteLine($"{itemName}: {result}"); //24
Output:
Item1: 24
You can try the following,
public void PrintQuantityForSingleItem(string itemName)
{
var res = Orders.Select(x=>x.Items).Sum(y => y.Where(z => z.Name == itemName).Sum(t => t.Quantity));
Console.WriteLine(res);
Console.ReadKey();
}
It prints 24
You can use Linq :
decimal counter = Orders
.SelectMany(o => o.Items)
.Where(i => i.Name == itemName)
.Sum(i => i.Quantity);
I hope you find this helpful.

How can I improve speed of DataTable group by Day method

I have about 300K rows in a DataTable. The first column is "utcDT" which contains a DateTime with minutes.
I want to group the data by Date into a list of "ReportDailyData". My method is below but takes around 8 seconds to run. I need to make this significantly faster.
Is there a better way to do this?
public class ReportDailyData
{
public DateTime UtcDT;
public double Day_Pnl;
public int TradeCount;
public int Volume;
public ReportDailyData(DateTime utcDT, double day_Pnl, int tradeCount, int volume)
{
UtcDT = utcDT;
Day_Pnl = day_Pnl;
TradeCount = tradeCount;
Volume = volume;
}
public string AsString()
{
return UtcDT.ToString("yyyyMMdd") + "," + Day_Pnl.ToString("F2") + "," + TradeCount + "," + Volume;
}
}
public static DataTable Data;
public static DataSpecification DataSpec;
public void Go()
{
//Fill Data and DataSpec elsewhere
var dailylist = GetDailyData();
}
public List<ReportDailyData> GetDailyData()
{
List<ReportDailyData> dailyDatas = new List<ReportDailyData>();
DateTime currentDT = DataSpec.FromDT.Date;
while (currentDT <= DataSpec.ToDT.Date)
{
var rowsForCurrentDT = Data.AsEnumerable().Where(x => x.Field<DateTime>("utcDT").Date == currentDT).ToList();
if (rowsForCurrentDT.Any())
{
double day_Pnl = rowsForCurrentDT.Sum(x => x.Field<double>("Bar_Pnl"));
var positions = rowsForCurrentDT.Select(x => x.Field<double>("Position")).ToList();
var deltas = positions.Zip(positions.Skip(1), (current, next) => next - current);
int tradeCount = deltas.Where(x => x != 0).Count();
int volume = (int)deltas.Where(x => x != 0).Sum(x => Math.Abs(x));
dailyDatas.Add(new ReportDailyData(currentDT, day_Pnl, tradeCount, volume));
}
else
{
dailyDatas.Add(new ReportDailyData(currentDT, 0, 0, 0));
}
currentDT = currentDT.AddDays(1);
}
return dailyDatas;
}
If I understood correctly - you want to perform grouping on some data collection, is that right?
If so - why not to use linq: GroupBy method?
A simple example is below:
void Main()
{
var data = new List<MyData>();
data.Add(new MyData() { UtcDT = DateTime.UtcNow, Volume = 1 });
data.Add(new MyData() { UtcDT = DateTime.UtcNow.AddDays(-1), Volume = 1 });
data.Add(new MyData() { UtcDT = DateTime.UtcNow.AddDays(-1), Volume = 4 });
data.Add(new MyData() { UtcDT = DateTime.UtcNow.AddDays(-2), Volume = 5 });
var result = GroupReportDataAndFormat(data);
}
public Dictionary<DateTime, int> GroupReportDataAndFormat(List<MyData> data)
{
return data.GroupBy(t => t.UtcDT.Date).ToDictionary(k => k.Key, v => v.Sum(s => s.Volume));
}
public class MyData
{
public DateTime UtcDT { get; set; }
public int Volume { get; set; }
}
Of course - for performance reasons, you probably should do grouping on database level (compose query to return your data, that is already grouped)
=== UPDATE =====
MainInMoon : I've updated solution to fit your case:
void Main()
{
var data = new List<MyData>();
data.Add(new MyData() { UtcDT = DateTime.UtcNow, DayPnl = 1, Positions = 3 });
data.Add(new MyData() { UtcDT = DateTime.UtcNow.AddDays(-1), DayPnl = 1, Positions = 4 });
data.Add(new MyData() { UtcDT = DateTime.UtcNow.AddDays(-1), DayPnl = 4, Positions = 5 });
data.Add(new MyData() { UtcDT = DateTime.UtcNow.AddDays(-2), DayPnl = 5, Positions = 6 });
var result = GroupReportDataAndFormat(data);
}
public Dictionary<DateTime, GroupResult> GroupReportDataAndFormat(List<MyData> data)
{
return data.GroupBy(t => t.UtcDT.Date).ToDictionary(
k => k.Key, v => new GroupResult
{
DayPnlSum = v.Sum(s => s.DayPnl),
Deltas = v.Select(t => t.Positions).Zip(v.Select(s => s.Positions).Skip(1), (current, next) => next - current)
});
}
public class GroupResult
{
public double DayPnlSum { get; set; }
public IEnumerable<double> Deltas { get; set; }
public int TradeCount
{
get
{
return Deltas.Where(x => x != 0).Count();
}
}
public int Volume
{
get
{
return (int)Deltas.Where(x => x != 0).Sum(x => Math.Abs(x));
}
}
}
public class MyData
{
public DateTime UtcDT { get; set; }
public int DayPnl { get; set; }
public double Positions { get; set; }
}
Of course, you can change TradeCount and Volume properties to be calculated during grouping (not lazy loaded)
I would advise: sort on the utcDT, then enumerate the result linearly and do the grouping and aggregation manually into a new data structure. For every new utcDT value you encounter, create a new ReportDailyData instance, then start aggregating the values into it until utcDT has the same value.

How can join to table column in single table using linq as full joining

In my project,
I have two tables
(1)Import_detail table (Date, Description,Parti_Name, Company_name,Amount ) and
(2)Export_detail table (Date, Description,Parti_Name, Company_name,Amount )
and i want to Show this to table detail as Balance sheet in DataGrid using linq.
here i am using union for joining to merge column but i don,t know how to get remain Balance
as this sheet.
i am using linq lambda Expression.
please help me.
here Code to union table
var query = dm.Import_detail.Select(r => r).Union( dm.Export_detail
.Select.Select(q => q));
Look at this example. may be it is what you want.
I have 3 classes
public class Import
{
public DateTime date { get; set; }
public decimal amount { get; set; }
}
public class Export
{
public DateTime date { get; set; }
public decimal amount { get; set; }
}
public class Result
{
public DateTime date { get; set; }
public decimal creditAmount { get; set; }
public decimal debitAmount { get; set; }
public decimal balanceAmount { get; set; }
}
And Select
var importList = new List<Import>()
{
new Import{date = DateTime.Parse("2009-01-02"), amount = 10000},
new Import{date = DateTime.Parse("2009-01-25"), amount = 6000}
};
var exportList = new List<Export>()
{
new Export{date = DateTime.Parse("2009-01-05"), amount = 500},
new Export{date = DateTime.Parse("2009-01-10"), amount = 1000},
new Export{date = DateTime.Parse("2009-01-11"), amount = 1500},
new Export{date = DateTime.Parse("2009-01-15"), amount = 4000},
new Export{date = DateTime.Parse("2009-01-28"), amount = 5000}
};
var temp = importList.Select(c => new Result { date = c.date, debitAmount = c.amount, creditAmount = 0, balanceAmount = 0 }).Union(
exportList.Select(c => new Result { date = c.date, debitAmount = 0, creditAmount = c.amount, balanceAmount = 0 })).GroupBy(c => c.date).Select(c => new Result { date = c.Key, creditAmount = c.Sum(g => g.creditAmount), debitAmount = c.Sum(g => g.debitAmount) }).OrderBy(c => c.date).ToList();
foreach (var item in temp)
item.balanceAmount = temp.Where(c => c.date < item.date).OrderByDescending(c => c.date).Select(c => c.balanceAmount).FirstOrDefault() + item.debitAmount - item.creditAmount;
Results:
debitAmount creditAmount balanceAmount
10000 0 10000
0 500 9500
0 1000 8500
0 1500 7000
0 4000 3000
6000 0 9000
0 5000 4000

How do you order by Greatest number in linq

I would like to print products in order of quantity.The product with a bigger total should be first.
What am I missing here as it's NOT printing in order or total
class Program
{
static void Main()
{
var products=new List<Product>
{
new Product {Name = "Apple", Total = 5},
new Product {Name = "Pear", Total = 10}
};
var productsByGreatestQuantity = products.OrderBy(x => x.Total);
foreach (var product in productsByGreatestQuantity)
{
System.Console.WriteLine(product.Name);
}
System.Console.Read();
}
}
public class Product
{
public string Name { get; set; }
public int Total { get; set; }
}
var data = products.OrderByDescending(x => x.Total);
Change:
var productsByGreatestQuantity = products.OrderBy(x => x.Total);
to:
var productsByGreatestQuantity = products.OrderByDescending(x => x.Total);

Categories