How can I improve speed of DataTable group by Day method - c#

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.

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

C# LINQ Group By multiple fields with Custom Properties

I'm trying to agregate a list of multiple propertys with Linq.
My Second field is a List of Strings + an other List of strings inside.
Here's a sample of my code :
using System;
using System.Collections.Generic;
using System.Linq;
public class RefValueData
{
public int ReferenceId { get; set; }
public int SiteId { get; set; }
public string SiteName { get; set; }
public string Code { get; set; }
public decimal UnitPoints { get; set; }
public List<TranslationData> Texts { get; set; }
}
public class TranslationData
{
public string Text { get; set; }
public List<TranslationValue> Translations { get; set; }
}
public class TranslationValue
{
public string Culture { get; set; }
public string TranslationText { get; set; }
}
public class Program
{
public static void Main()
{
var values = new List<RefValueData>
{
new RefValueData(){
ReferenceId = 4,
Code = "Code",
SiteId = 2,
SiteName = "Paris",
UnitPoints = 50,
Texts = new List<TranslationData>
{
new TranslationData(){
Text = "A",
Translations = new List<TranslationValue>
{
new TranslationValue() { Culture = "FR-fr", TranslationText = "Bonjour" },
new TranslationValue() { Culture = "ES-es", TranslationText = "Hola" },
}
}
}
},
new RefValueData()
{
ReferenceId = 5,
Code = "Code",
SiteId = 4,
SiteName = "Lyon",
UnitPoints = 50,
Texts = new List<TranslationData>
{
new TranslationData(){
Text = "A",
Translations = new List<TranslationValue>
{
new TranslationValue() { Culture = "FR-fr", TranslationText = "Bonjour" },
new TranslationValue() { Culture = "ES-es", TranslationText = "Hola" },
}
}
}
},
new RefValueData()
{
ReferenceId = 6,
Code = "Code",
SiteId = 3,
SiteName = "Paris",
UnitPoints = 52,
Texts = new List<TranslationData>
{
new TranslationData(){
Text = "B",
Translations = new List<TranslationValue>
{
new TranslationValue() { Culture = "FR-fr", TranslationText = "Salut" },
new TranslationValue() { Culture = "ES-es", TranslationText = "Ciao" },
}
}
}
}
};
var values2 = values
.Distinct()
.GroupBy(x => new
{
x.UnitPoints,
x.Texts
})
.Select(x => new
{
x.Key.UnitPoints,
Texts = x.Key.Texts,
Site = x.Select(y=>y.SiteName)
})
.ToList();
Console.WriteLine(values2.Count);
}
}
I want to have only two lines in my values2 list, but everytime it returns me the whole list.
When I only group by Unit Point, it's work great !
I tried to group the first two lines of my list with some custom Linq query but it doesn't work at all...
Any help / advice is much appreciated :) !
EDIT :
I also tried with an override of the Equals methods like this, but I can't make it work :
public class TranslationValue
{
public string Culture { get; set; }
public string TranslationText { get; set; }
public override bool Equals(object obj)
{
var other = obj as TranslationValue;
if (other == null)
{
return false;
}
return Culture == other.Culture && TranslationText == other.TranslationText;
}
public override int GetHashCode()
{
var hashCode = -2095322044;
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Culture);
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(TranslationText);
return hashCode;
}
}
public class TranslationData
{
public string Text { get; set; }
public List<TranslationValue> Translations { get; set; }
public override bool Equals(object obj)
{
var other = obj as TranslationData;
if (other == null)
{
return false;
}
return Text == other.Text && Translations.SequenceEqual(other.Translations);
}
public override int GetHashCode()
{
var hashCode = -1551681861;
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Text);
hashCode = hashCode * -1521134295 + EqualityComparer<List<TranslationValue>>.Default.GetHashCode(Translations);
return hashCode;
}
}
EDIT2 : Here's my 'real' code :
var values = referenceValues.Select(value => new
{
ReferenceId = value.ReferenceId,
SiteId = value.Reference.SiteId ?? -1,
SiteName = value.Reference.Site.Name ?? allSitesName,
Code = value.Code,
UnitPoints = value.UnitPoints,
Texts = // Type: List<TranslationData> , but it will not use the TranslationDataList class that normally work thanks to your help
value.ReferenceValueTexts.Select(text =>
new TranslationData
{
Text = text.Text, // string
Translations = text.TranslationDataValues.Select(translation => // List<TranslationValue>
new TranslationValue {
Culture = translation.Language.StrCulture,
TranslationText = translation.Value
}).ToList()
}).ToList()
}
Julien.
Here's one solution. It works for the sample code that you wrote. But it needs a little work to be robust:
// and also change the declarations in the main method to: new TranslationDataList
public class TranslationDataList : List<TranslationData>
{
public override int GetHashCode()
{
int hash = 13;
// string.GetHashCode() is not reliable. This should be an algorithm that returns the same value for two different lists that contain the same data
foreach (var data in this)
hash = (hash * 7) + data.Text.GetHashCode();
return hash;
}
public override bool Equals(object obj)
{
var other = obj as TranslationDataList;
if (other == null) return false;
if (other.Count != Count) return false;
// write the equality logic here. I don't know if it's ok!
for (int i = 0; i < other.Count; i++)
if (other[i].Text != this[i].Text)
return false;
return true;
}
}
First of all you should add a constructor to the TranslationDataList:
public class TranslationDataList : List<TranslationData>
{
public TranslationDataList(IEnumerable<TranslationData> translationData)
: base(translationData)
{ }
// other members ...
}
Now you can use the TranslationDataList in your query:
var values = referenceValues.Select(value => new
{
ReferenceId = value.ReferenceId,
SiteId = value.Reference.SiteId ?? -1,
SiteName = value.Reference.Site.Name ?? allSitesName,
Code = value.Code,
UnitPoints = value.UnitPoints,
Texts = new TranslationDataList( value.ReferenceValueTexts.Select(text =>
new TranslationData
{
Text = text.Text, // string
Translations = text.TranslationDataValues.Select(translation => // List<TranslationValue>
new TranslationValue {
Culture = translation.Language.StrCulture,
TranslationText = translation.Value
}).ToList()
})); // don't ToList() here anymore
}
And here is another solution:
The GroupBy method takes an IEqualityComparer that can do the responsibility of comparing items for the grouping. But the problem is you used an anonymous type for the key in your grouping "GroupBy(x=>new{x.UnitPoints, x.Texts})". First we have to create a class to play the key role:
public class Key
{
public Key(decimal unitPoints, List<TranslationData> texts)
{
UnitPoints = unitPoints;
Texts = texts;
}
public decimal UnitPoints { get; set; }
public List<TranslationData> Texts { get; set; }
}
then we can implement the comparer:
public class Comparer : IEqualityComparer<Key>
{
public bool Equals(Key x, Key y)
{
if (x.UnitPoints != y.UnitPoints) return false;
if (!ListsAreEqual(x.Texts, y.Texts)) return false;
return true;
}
private bool ListsAreEqual(List<TranslationData> x, List<TranslationData> y)
{
if (x.Count != y.Count) return false;
for (int i = 0; i < x.Count; i++)
if (x[i].Text != y[i].Text)
return false;
return true;
}
public int GetHashCode(Key key)
{
int hash = 13;
hash = (hash * 7) + key.UnitPoints.GetHashCode();
foreach (var data in key.Texts)
hash = (hash * 7) + data.Text.GetHashCode();
return hash;
}
}
and finally this is what your query will look like:
var values2 = values
.Distinct()
.GroupBy(x => new Key(x.UnitPoints, x.Texts), new Comparer())
.Select(x => new
{
x.Key.UnitPoints,
Texts = x.Key.Texts,
Site = x.Select(y => y.SiteName)
}).ToList();
I think the first solution (creating the customized list class) is better, because you can also refactor your code and extract some logic to it.

How to Read This Text File and store in a list using C#

The Text File Data is Like Below:
S.No Name Description Quantity Rate Discount Amount
1 Apple Friut is 12 24.02 0 242
Good for
health
2 Orange Friut 5 12.22 3 128
3 Banana Friut 5 12.22 3 128
4 Grapes Friut 5 12.22 3 128
I want to add all the Rows& Columns in list but Description column have multiple Rows in single item. How can I Solve this. I add My Existing Code Here:
My Existing Code is as follows:
class Program
{
static void Main(string[] args)
{
var dd = File.ReadAllLines(
"C:\\Users\\Trainee\\Desktop\\Saravanan_Test\\27.8.2018\\Inputfile.txt")
.Skip(1)
.Where(s => s.Length > 1)
.Select(x => splits(x)).ToList();
foreach (var item in dd)
{
Console.WriteLine(item.id+"\t"
+ item.Name+"\t"
+ item.Description+"\t"
+ item.Quantity+"\t"
+ item.Rate+"\t"
+ item.Discount+"\t"
+ item.Amount);
}
Console.ReadKey();
}
private static Class1 splits(string x)
{
var columns = x.Split('\t').Where(c => c != "").ToList();
return new Class1
{
id = Convert.ToInt32(columns[0]),
Name = columns[1],
Description = columns[2],
Quantity = Convert.ToInt32(columns[3]),
Rate = Convert.ToDouble(columns[4]),
Discount = Convert.ToInt32(columns[5]),
Amount = int.Parse(columns[6])
};
}
}
class Class1
{
public int id { get; set; }
public string Name { get; set; }
public String Description { get; set; }
public int Quantity { get; set; }
public double Rate { get; set; }
public int Discount { get; set; }
public int Amount { get; set; }
}
I want to store data into list like:
list.Add(new{ sno=1, Name="Apple",
Description="Friut is good for Health",
Quantity=12, Rate=24.02, Discount=0,
Amount=242 });
Thanks in Advance.
NOTE: This solution is based on the file shared in question. Data is separated by spaces and format is not advisable to use. Answering to help person with content format he has. Tested and working.
static void Main(string[] args)
{
List<Data> list = new List<Data>();
var dd = File.ReadAllLines(#"C:\Users\XXXX\Desktop\test.txt")
.Skip(1)
.Where(s => s.Length > 1).ToList();
foreach (var item in dd)
{
var columns = item.Split('\t').Where(c => c.Trim() != string.Empty).ToList();
if (columns != null && columns.Count > 0)
{
int id;
if (int.TryParse(columns[0], out id))
{
list.Add(new Data()
{
id = Convert.ToInt32(columns[0]),
Name = columns[1],
Description = columns[2],
Quantity = Convert.ToInt32(columns[3]),
Rate = Convert.ToDouble(columns[4]),
Discount = Convert.ToInt32(columns[5]),
Amount = int.Parse(columns[6])
});
}
else
{
list.Last().Description += columns[0];
}
}
}
Console.ReadLine();
}

Moving some models to ViewModel

I have Logging model that related to db table
Here is code:
public partial class Logging
{
public string Imei { get; set; }
public DateTime CurDateTime { get; set; }
public Nullable<System.DateTime> GPSDateTime2 { get; set; }
public Nullable<decimal> Latitude2 { get; set; }
public Nullable<decimal> Longitude2 { get; set; }
public int Speed { get; set; }
public Nullable<int> Datatype { get; set; }
public int Id { get; set; }
}
I want to calculate difference and decided to create ViewModel
Here is code
public class HeatmapViewModel:Logging
{
public TimeSpan? FirstStartDifference
{
get
{
if (CurDateTime != null)
{
var midnight = new DateTime(CurDateTime.Year, CurDateTime.Month, CurDateTime.Day, 00, 00, 00);
var difference = CurDateTime - midnight;
return difference;
}
return null;
}
}
public TimeSpan? LastStartDifference
{
get
{
if (CurDateTime != null)
{
var midnight = new DateTime(CurDateTime.Year, CurDateTime.Month, CurDateTime.Day, 23, 59, 00);
var difference = midnight - CurDateTime;
return difference;
}
return null;
}
}
public int coeff = 2;
public int Difference;
}
And on back-end I have this method
List<HeatmapViewModel> items = new List<HeatmapViewModel>();
var firstitem = ctx.Loggings.Where(x => x.Datatype == 2).AsEnumerable().Select(
x => new HeatmapViewModel
{
Longitude2 = x.Longitude2,
Latitude2 = x.Latitude2,
Difference = (int)(x.FirstStartDifference?.TotalMinutes ?? -1) * x.coeff
}).FirstOrDefault();
var lastItem = ctx.Loggings.Where(x => x.Datatype == 2).AsEnumerable().Select(
x => new HeatmapViewModel
{
Longitude2 = x.Longitude2,
Latitude2 = x.Latitude2,
Difference = (int)(x.LastStartDifference?.TotalMinutes ?? -1) * x.coeff
}).LastOrDefault();
But on this row Difference = (int)(x.FirstStartDifference?.TotalMinutes ?? -1) * x.coeff, I have error
Severity Code Description Project File Line Suppression State
Error CS1061 'Logging' does not contain a definition for 'FirstStartDifference' and no extension method 'FirstStartDifference' accepting a first argument of type 'Logging' could be found (are you missing a using directive or an assembly reference?) Heatmap C:\Users\nemes\source\repos\Heatmap\Heatmap\Controllers\HomeController.cs 28 Active
How I can use property from VIewModel
Use multi-line lambda instead:
var firstitem = Loggings.Where(x => x.Datatype == 2).AsEnumerable().Select(
x =>
{
var vm = new HeatmapViewModel
{
Longitude2 = x.Longitude2,
Latitude2 = x.Latitude2
};
vm.Difference = (int)(vm.FirstStartDifference?.TotalMinutes ?? -1) * vm.coeff;
return vm;
}).FirstOrDefault();
Note: Please read comments below your post, because you are mixing types. Variable x is of type Logging

Calculating totals in linq query

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.

Categories