LINQ to Object - How to implement dynamic SELECT projection of sub elements - c#

I have several classes of business logic:
public class Client {
public string Code { get; set; } = string.Empty;
public string Status { get; set; } = string.Empty;
public string Account { get; set; } = string.Empty;
public Total Total { get; set; } = new Total();
public List<Month> Months { get; set; } = new List<Month>();
}
public class Month {
public int Number { get; set; } = 0;
public string Name { get; set; } = string.Empty;
public DateTime Start { get; set; } = new DateTime();
public DateTime End { get; set; } = new DateTime();
public Total Summary { get; set; } = new Total();
}
public class Total {
public int Count { get; set; } = 0;
public decimal Sum { get; set; } = 0.0m;
}
which are instanced as follows:
List<Client> clients = new List<Client>() {
new Client {
Code = "7002.70020604",
Status = "Active",
Account = "7002.915940702810005800001093",
Total = new Total {
Count = 9,
Sum = 172536.45m
},
Months = new List<Month>() {
new Month {
Number = 0,
Name = "January",
Start = new DateTime(2021, 1, 1, 0, 0, 0),
End = new DateTime(2021, 1, 31, 23, 59, 59),
Summary = new Total {
Count = 6,
Sum = 17494.50m
}
},
new Month {
Number = 1,
Name = "February",
Start = new DateTime(2021, 2, 1, 0, 0, 0),
End = new DateTime(2021, 2, 28, 23, 59, 59),
Summary = new Total {
Count = 3,
Sum = 155041.95m
}
},
new Month {
Number = 2,
Name = "March",
Start = new DateTime(2021, 3, 1, 0, 0, 0),
End = new DateTime(2021, 3, 31, 23, 59, 59),
Summary = new Total {
Count = 0,
Sum = 0.0m
}
}
}
},
new Client {
Code = "7002.70020604",
Status = "Active",
Account = "7002.800540702810205800001093",
Total = new Total {
Count = 4,
Sum = 16711.21m
},
Months = new List<Month>() {
new Month {
Number = 0,
Name = "January",
Start = new DateTime(2021, 1, 1, 0, 0, 0),
End = new DateTime(2021, 1, 31, 23, 59, 59),
Summary = new Total {
Count = 0,
Sum = 0.0m
}
},
new Month {
Number = 1,
Name = "February",
Start = new DateTime(2021, 2, 1, 0, 0, 0),
End = new DateTime(2021, 2, 28, 23, 59, 59),
Summary = new Total {
Count = 0,
Sum = 0.0m
}
},
new Month {
Number = 2,
Name = "March",
Start = new DateTime(2021, 3, 1, 0, 0, 0),
End = new DateTime(2021, 3, 31, 23, 59, 59),
Summary = new Total {
Count = 4,
Sum = 16711.21m
}
}
}
}
};
I'm trying to arrange aggregate data of a view like this:
+---------------+--------+------------------+-------------------+------------------+-------------------+
| Code | Status | January | February | March | Total |
| | +-------+----------+-------+-----------+-------+----------+-------+-----------+
| | | Count | Sum | Count | Sum | Count | Sum | Count | Sum |
+---------------+--------+-------+----------+-------+-----------+-------+----------+-------+-----------+
| 7002.70020604 | Active | 6 | 17494.50 | 3 | 155041.95 | 4 | 16711.21 | 13 | 189247.66 |
+---------------+--------+-------+----------+-------+-----------+-------+----------+-------+-----------+
using projection like this:
clients
.GroupBy(x => x.Code)
.Select(y => new {
Code = y.First().Code,
Status = y.First().Status,
Account = y.First().Account,
Total = new {
Count = y.Sum(z => z.Total.Count),
Sum = y.Sum(z => z.Total.Sum)
},
Months = new {
/*
?
*/
}
});
But I can't project the data by month. Assuming the date range (months) can be more than just this example. Please help!
Full interactive code listing at dotnetfiddle

You can use SelectMany to get months out of y and then group by month similarly as you group by code:
//...
Months = y
.SelectMany(client => client.Months)
.GroupBy(month => month.Name, (_, months) => new {
Number = months.First().Number,
Name = months.First().Name,
Start = months.First().Start,
End = months.First().End,
Summary = new {
Count = months.Sum(z => z.Summary.Count),
Sum = months.Sum(z => z.Summary.Sum)
}
}).ToList()
//...
That being said I don't suggest to use y.First() or months.First() more than once in each function because it makes an enumeration each time it is used. The following should in general have better performance:
(_, months) => {
var month = months.First();
return new {
Number = month.Number,
Name = month.Name,
Start = month.Start,
End = month.End,
Summary = new {
Count = months.Sum(z => z.Summary.Count),
Sum = months.Sum(z => z.Summary.Sum)
}
}
}
which is also not ideal because we're still making 3 enumerations here (1 enumeration in .First() and 1 enumeration for every .Sum(...)).
Even better approach would be to use Aggregate function which will do only a single enumeration:
(_, months) => months
.Aggregate((res, nextVal) => new Month {
Number = nextVal.Number,
Name = nextVal.Name,
Start = nextVal.Start,
End = nextVal.End,
Summary = new Total {
Count = res.Summary.Count + nextVal.Summary.Count,
Sum = res.Summary.Sum + nextVal.Summary.Sum
}
})

This LINQ query should prepare data for visualization:
clients
.GroupBy(x => new {x.Code, x.Status})
.Select(g => new
{
Code = g.Key
MonthsSummary = g.SelectMany(x => x.Months)
.OrderBy(x => x.Start)
.GroupBy(x => new {x.Start, x.Name})
.Select(gm => new
{
gm.Key.Name,
Count = gm.Sum(x => x.Summary.Count),
Sum = gm.Sum(x => x.Summary.Sum),
})
.ToList()
});

Related

How to extract a specific value from the linq query?

I am building a NET 5 API and am unable to extract and calculate something. I have a table StockTransaction which among other has property Quantity (I skipped some properties for brevity):
public class StockTransaction : BaseEntity
{
public int Id { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
public bool Purchase { get; set; }
public int Resolved { get; set; }
}
Suppose I have 7 transactions, all of which are purchase:
List<StockTransaction> list = new List<StockTransaction>
{
new StockTransaction {Id = 1, Purchase = true, Quantity = 10, Resolved = 5, Price = 50},
new StockTransaction {Id = 2, Purchase = true, Quantity = 5, Resolved = 5, Price = 70},
new StockTransaction {Id = 3, Purchase = true, Quantity = 8, Resolved = 8, Price = 25},
new StockTransaction {Id = 4, Purchase = true, Quantity = 7, Resolved = 5, Price = 77},
new StockTransaction {Id = 5, Purchase = true, Quantity = 1, Resolved = 1, Price = 23},
new StockTransaction {Id = 6, Purchase = true, Quantity = 3, Resolved = 0, Price = 14},
new StockTransaction {Id = 7, Purchase = true, Quantity = 2, Resolved = 0, Price = 17},
};
And I would like to get the value of the last 7 quantities, which in this case gives 176 ((2 x 17) + (3 x 14) + (1 x 23) + (1 x 77)). (How) can this be done? Every help and hint is more then appreciated...
You can use
var last3=list.OrderByDescending(x=>x.CreatedDate).Take(3).Select(x=>x.Quantity * x.Price).Sum();
var requiredSum=last3+list.Where(x=>x.id==4).Select(x=>x.Price).FirstOrDefault();
Although you have tagged your question with LINQ, do it with a normal loop:
private static decimal SumOfLastTransaction(IEnumerable<StockTransaction> stockTransactions, int max)
{
decimal result = 0;
int sum = 0;
foreach(var stockTransaction in stockTransactions.OrderByDescending(x => x.Id))
{
if(sum + stockTransaction.Quantity <= max)
{
result += stockTransaction.Quantity * stockTransaction.Price;
sum += stockTransaction.Quantity;
}
else
{
result += (max - sum) * stockTransaction.Price;
return result;
}
}
return result;
}
You loop over the last items in your list. You sum the amount of transactions and check whether it is smaller than your maximum. As long as they are smaller, you can add the amount of transactions. If not, you check how much is missing until your maximum is reached and substract this from your quantity.
Online demo: https://dotnetfiddle.net/9oM4pj

Asserting in MSTEST that a collection OrderBy worked properly

Good morning. I am writing a unit test to validate that my API handler is sorting the collection properly. As you can see, it's mocked data and I intentionally created the test data out of order to test the OrderBy functionality. Once the handler returns the DTO collection, I want to validate that the "act" collection is in the same order as my "expectedDTO" collection. Here's the code:
[TestMethod]
public async Task GetByDay_ReturnsDtoList_WhenFound()
{
var testData = TestMethodData();
var expectedCalendarDto = ExpectedDto();
var testCalendar = testData;
var query = new GetCalendarByDaysQuery();
_mockCalendarRepo.Setup(m => m.GetItemsAsync(It.IsAny<ISpecification<CalendarDay>>(), null, null))
.ReturnsAsync(testCalendar);
var sut = new GetCalendarByDaysHandler(_mockCalendarRepo.Object, _mapper);
var act = await sut.HandleAsync(query);
Assert.IsNotNull(act);
Assert.IsInstanceOfType(act, typeof(IEnumerable<CalendarDto>));
Assert.AreEqual(expectedCalendarDto, act);
}
private GetItemsResult<IEnumerable<CalendarDay>> TestMethodData()
{
var testData = new GetItemsResult<IEnumerable<CalendarDay>>();
testData.ContinuationToken = null;
testData.Results = new List<CalendarDay>()
{
new CalendarDay { Id = "4-5-4|2021-04-01", FiscalYear = 2021, CalendarType = "4-5-4", Quarter = 2, Period = 4, CalendarDate = "2021-04-01", WeekOfYear = 13, DayOfYear = 61, WeekOfPeriod = 1 },
new CalendarDay { Id = "4-5-4|2021-08-01", FiscalYear = 2021, CalendarType = "4-5-4", Quarter = 3, Period = 8, CalendarDate = "2021-08-01", WeekOfYear = 24, DayOfYear = 121, WeekOfPeriod = 1 },
new CalendarDay { Id = "4-5-4|2021-01-01", FiscalYear = 2021, CalendarType = "4-5-4", Quarter = 1, Period = 1, CalendarDate = "2021-01-01", WeekOfYear = 1, DayOfYear = 1, WeekOfPeriod = 1 }
};
return testData;
}
private IEnumerable<CalendarDto> ExpectedDto()
{
var testDto = new List<CalendarDto>()
{
new CalendarDto { FiscalYear = 2021, CalendarType = "4-5-4", Quarter = 1, Period = 1, CalendarDate = "2021-01-01", WeekOfYear = 1, DayOfYear = 1, WeekOfPeriod = 1 },
new CalendarDto { FiscalYear = 2021, CalendarType = "4-5-4", Quarter = 2, Period = 4, CalendarDate = "2021-04-01", WeekOfYear = 13, DayOfYear = 61, WeekOfPeriod = 1 },
new CalendarDto { FiscalYear = 2021, CalendarType = "4-5-4", Quarter = 3, Period = 8, CalendarDate = "2021-08-01", WeekOfYear = 24, DayOfYear = 121, WeekOfPeriod = 1 }
};
return testDto;
}
Currently, in trying to compare the two collections, the test is failing saying that they aren't equal.
Assert.AreEqual failed. Expected:<System.Collections.Generic.List`1[cscentcalendar.infrastructure.DTOs.CalendarDto]>. Actual:<System.Collections.Generic.List`1[cscentcalendar.infrastructure.DTOs.CalendarDto]>.
Not sure what I'm doing wrong. Any assistance would be greatly appreciated.
I believe I solved the problem by using fluent assertion and adding the following line of code to test the two collections. Let me know if this is incorrect. Thanks again!
expectedCalendarDto.Should().BeEquivalentTo(act);

Date time range of shift in a day split into intervals of 10 minutes C#

I am wondering if anyone can help me, I have a collection of start times and end times for a work shift of a day. the hours can be spread across the day. I am trying group by the hour in day (like 0, 1, 2...23, 24) and show that hour in intervals of 10 minutes or worked or not worked. So, I would like to get the end result like below:
I want to able distinguish between worked and not on a hourly basis, the input provides the worked but I have calculate the not worked, I created a method for handling if a time falls outside a 10 minute interval it will set to the nearest one. Method called DoRounding:
Example:
9 am => 0 - 10 Worked
10 - 20 Not Worked
20 - 30 Worked
30 - 40 Worked
40 - 50 Worked
50 - 60 Worked
The time that fails out of the period can be be handled like so
private static int DoRounding(DateTime date)
{
if (Enumerable.Range(0, 10).Contains(date.Minute))
return 0;
if (Enumerable.Range(10, 20).Contains(date.Minute))
return 20;
if (Enumerable.Range(20, 30).Contains(date.Minute))
return 30;
if (Enumerable.Range(30, 40).Contains(date.Minute))
return 40;
if (Enumerable.Range(40, 50).Contains(date.Minute))
return 50;
return 60;
}
My method to explode the workblock (I was trying to break down the work period into hours here so I could add the missing parts in another method)
public static IEnumerable<Tuple<int, DateTime>> CalculateIntervals(WorkPeriod workBlock)
{
yield return new Tuple<int, DateTime>(workBlock.StartTime.Hour, workBlock.StartTime);
var dateTime = new DateTime(workBlock.StartTime.Year, workBlock.StartTime.Month, workBlock.StartTime.Day, workBlock.StartTime.Hour, workBlock.StartTime.Minute, 0, workBlock.StartTime.Kind).AddHours(1);
while (dateTime < workBlock.EndTime)
{
yield return new Tuple<int, DateTime>(dateTime.Hour, dateTime);
dateTime = dateTime.AddHours(1);
}
yield return new Tuple<int, DateTime>(workBlock.EndTime.Hour, workBlock.EndTime);
}
My attempt at grouping (I want to group into the time slots here to the hour and the intervals such as 1 pm, 0 - 10 minutes and mark it as worked but if an interval was missing from here add it as not worked)
public static void WorkingHourIntervalStrings(List<WorkPeriod> WorkingHours)
{
var output = new List<Tuple<int, DateTime>>();
foreach (var result in WorkingHours.Select(CalculateIntervals))
output.AddRange(result);
output = output.OrderBy(x => x.Item2).ToList();
var test = output.GroupBy(
p => p.Item1,
p => p.Item2.Minute,
(key, g) => new { Worked = key, Minute = g.ToList() });
}
Class
public class WorkPeriod
{
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
}
Calling
var input = new List<WorkPeriod>
{
new WorkPeriod { StartTime = new DateTime(2020, 5, 25, 9, 40, 56), EndTime = new DateTime(2020, 5, 25, 14, 22, 12) },
new WorkPeriod { StartTime = new DateTime(2020, 5, 25, 9, 50, 56), EndTime = new DateTime(2020, 5, 25, 14, 59, 12) },
new WorkPeriod { StartTime = new DateTime(2020, 5, 25, 13, 40, 56), EndTime = new DateTime(2020, 5, 25, 18, 22, 12) },
new WorkPeriod { StartTime = new DateTime(2020, 5, 25, 19, 40, 56), EndTime = new DateTime(2020, 5, 25, 23, 22, 12) }
};
TimeIntervals.WorkingHourIntervalStrings(input);
Possible output structure:
public class Interval
{
public Interval() => Contents = new List<Contents>();
public int Hour { get; set; }
public List<Contents> Contents { get; set; }
}
public class Contents
{
public bool Worked { get; set; }
public int Start { get; set; }
public int End { get; set; }
}
Based on your above explanations I would do the following:
public class Interval
{
public Interval() => Contents = new List<Contents>();
public int Hour { get; set; }
public List<Contents> Contents { get; set; }
}
public class Contents
{
public bool Worked { get; set; }
public int Start { get; set; }
//public int End { get; set; }
public int End => Start + 10;
}
public class WorkPeriod
{
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
}
Look at the Contents class. The End property is autocalculated from the Start one.
Then I would create the following Calculator class:
public class Calculator
{
public bool[] WorkedIntervals = new bool[24 * 6];
private void SetWork(int Hour, int Min)
{
int pos = Hour * 6 + Min / 10;
WorkedIntervals[pos] = true;
}
private void UpdateIntervals(WorkPeriod period)
{
var cur = period.StartTime;
while (cur < period.EndTime)
{
SetWork(cur.Hour, cur.Minute);
cur = cur.AddMinutes(10);
}
}
private void UpdateIntervals(List<WorkPeriod> periods)
{
foreach (var l in periods)
UpdateIntervals(l);
}
public IEnumerable<Interval> CalcIntervals(List<WorkPeriod> periods)
{
var minTime = (from p in periods
select p.StartTime).Min();
var maxTime = (from p in periods
select p.EndTime).Max();
UpdateIntervals(periods);
for(int h=minTime.Hour; h<=maxTime.Hour; ++h)
{
int pos = h * 6;
var intrvl = new Interval() { Hour = h };
for (int m=0; m<=5; m++)
{
if (WorkedIntervals[pos + m])
intrvl.Contents.Add(new Contents() { Start = m * 10, Worked = true });
else
intrvl.Contents.Add(new Contents() { Start = m * 10, Worked = false });
}
yield return intrvl;
}
}
}
The idea is that you have to flatten all your time intervals to an array of 144 boolean values (24*6) that represents if each of this 10 minute time interval has been worked or not. eg. if the 7th index of the array is true then it means that at Hour 1 (hour 0 is in indexes 0-5) the 10-20 min interval has been worked.
Then, on your main function you do the following.
var input = new List<WorkPeriod>
{
new WorkPeriod { StartTime = new DateTime(2020, 5, 25, 9, 40, 56), EndTime = new DateTime(2020, 5, 25, 14, 22, 12) },
new WorkPeriod { StartTime = new DateTime(2020, 5, 25, 9, 50, 56), EndTime = new DateTime(2020, 5, 25, 14, 59, 12) },
new WorkPeriod { StartTime = new DateTime(2020, 5, 25, 13, 40, 56), EndTime = new DateTime(2020, 5, 25, 18, 22, 12) },
new WorkPeriod { StartTime = new DateTime(2020, 5, 25, 19, 40, 56), EndTime = new DateTime(2020, 5, 25, 23, 22, 12) }
};
Calculator ints = new Calculator();
var res = ints.CalcIntervals(input).ToList();
The res list should contain the hour-intervals from the minimum StartTime to the maximum EndTime with their respected sub-lists.

Getting first time-In and last time-Out covering the following day based on a date Collection using linq

I am having a problem getting structured First Entry and Last Entry on the Biometric reading when it exceeds the following day when the user time out. But if the time in and time out is on the same day i can get it formatted the way i want..
here is my code:
IEnumerable<biometric> dtrs = new List<biometric>()
{
new biometric{Id = 1, InOut = 0, DateTime = new DateTime(2013,5,5,8,0,0)},
new biometric{Id = 2, InOut = 0, DateTime = new DateTime(2013,5,5,8,0,5)},
new biometric{Id = 3, InOut = 0, DateTime = new DateTime(2013,5,5,8,1,0)},
new biometric{Id = 4, InOut = 0, DateTime = new DateTime(2013,5,5,8,2,0)},
//here is my problem getting this paired to
new biometric{Id = 5, InOut = 0, DateTime = new DateTime(2013,5,5,18,0,0)},
new biometric{Id = 1, InOut = 1, DateTime = new DateTime(2013,5,5,18,0,0)},
new biometric{Id = 2, InOut = 1, DateTime = new DateTime(2013,5,5,17,5,5)},
new biometric{Id = 3, InOut = 1, DateTime = new DateTime(2013,5,5,17,5,10)},
new biometric{Id = 4, InOut = 1, DateTime = new DateTime(2013,5,5,17,10,0)},
//this Entry here
new biometric{Id = 5, InOut = 1, DateTime = new DateTime(2013,5,6,3,0,0)},
};
var asd = dtrs.GroupBy(x => new { x.Id, x.DateTime.Date }, (key, group) => new
{
Key1 = key.Date,
Key2 = key.Id,
Result = group.OrderBy(a => a.DateTime).ToList()
})
//checks if the grouping result has one timein and 1 timeout or more
.Where(a => a.Result.Where(z => z.InOut == 1).Count() >= 1 && a.Result.Where(z => z.InOut == 0).Count() >= 1)
.Select(a => new dtr() { employeeId = a.Key2, TimeIn = a.Result.FirstOrDefault(b => b.InOut == 1).DateTime, TimeOut = a.Result.LastOrDefault(c => c.InOut == 0).DateTime });
private class biometric
{
public int Id { get; set; }
public DateTime DateTime { get; set; }
public int InOut { get; set; }
}
private class dtr
{
public int employeeId { get; set; }
public DateTime TimeIn { get; set; }
public DateTime TimeOut { get; set; }
}
I want to pair this
new biometric{Id = 5, InOut = 0, DateTime = new DateTime(2013,5,5,18,0,0)},
to this
new biometric{Id = 5, InOut = 1, DateTime = new DateTime(2013,5,6,3,0,0)},
any workaround or suggestions??
var asd3 = from d1 in dtrs
join d2 in dtrs on d1.Id equals d2.Id
where d1.InOut ==0
&& d2.InOut == 1
&& d2.DateTime > d1.DateTime
&& d2.DateTime.AddHours(-18) < d1.DateTime
orderby d1.DateTime.Date, d1.Id
select new dtr {employeeId = d1.Id,
TimeIn=d1.DateTime,
TimeOut= d2.DateTime};
I added a second days worth of data, to assure that the filtering was right. This assume that no one works more than 18 hours in a day. Assuming that most people work a standard 8 hours, that could be set as high as 31 hours.
employeeId TimeIn TimeOut
1 05/05/2013 08:00:00.000 05/05/2013 18:00:00.000
2 05/05/2013 08:00:05.000 05/05/2013 17:05:05.000
3 05/05/2013 08:01:00.000 05/05/2013 17:05:10.000
4 05/05/2013 08:02:00.000 05/05/2013 17:10:00.000
5 05/05/2013 18:00:00.000 05/06/2013 03:00:00.000
1 05/06/2013 08:00:00.000 05/06/2013 18:00:00.000
2 05/06/2013 08:00:05.000 05/06/2013 17:05:05.000
3 05/06/2013 08:01:00.000 05/06/2013 17:05:10.000
4 05/06/2013 08:02:00.000 05/06/2013 17:10:00.000
5 05/06/2013 18:00:00.000 05/07/2013 03:00:00.000
Also, as a side note, your original code contain a.Result.Where(z => z.InOut == 1).Count() >= 1. That could be reduced to a.Result.Any(z => z.InOut == 1) which, beside being be concise & easier to read, would probably execute faster.
UPDATE:
Adding these two records (Employee 1 goes to lunch)
new biometric{Id = 1, InOut = 1, DateTime = new DateTime(2013,5,5,12,0,0)},
new biometric{Id = 1, InOut = 0, DateTime = new DateTime(2013,5,5,13,0,1)},
var sorted = dtrs.OrderBy (d =>d.Id).ThenBy (d =>d.DateTime ).ToList();
var zipped = sorted.Where (d =>d.InOut==0 ).Zip(sorted.Where (s =>s.InOut==1),
(i,o)=>{
Debug.Assert(i.Id == o.Id);
return new dtr
{
employeeId = i.Id,
TimeIn=i.DateTime,
TimeOut= o.DateTime
};
}).OrderBy (d =>d.TimeIn);
zipped.Dump();

LINQ Anonymous type - bypass Group By

I have LINQ sql (see below, thanks to Cameron ). I am trying to get a property (ItemCode) from class First without using that in Group by clause.
How do I do that?
Don't use First.ItemCode in group by but still want it in output by First.Begin, First.End order by decending.
public class First
{
public string Account;
public DateTime Begin;
public DateTime End;
public decimal Amount;
public string ItemCode;
}
public class Second
{
public string Account;
public DateTime Begin;
public DateTime End;
public decimal Amount;
}
List<First> firstAccount = new List<First>();
List<Second> secondAccount = new List<Second>();
firstAccount.Add(new First()
{
Account = "1234",
Begin = new DateTime(2014, 5, 13),
End = new DateTime(2014, 6, 12),
Amount = 9999,
ItemCode = "AAA"
});
firstAccount.Add(new First()
{
Account = "1234",
Begin = new DateTime(2014, 6, 13),
End = new DateTime(2014, 7, 7),
Amount = 1000,
ItemCode = "AAA"
});
firstAccount.Add(new First()
{
Account = "1234",
Begin = new DateTime(2014, 6, 13),
End = new DateTime(2014, 7, 14),
Amount = 0,
ItemCode = ""
});
firstAccount.Add(new First()
{
Account = "1234",
Begin = new DateTime(2014, 7, 7),
End = new DateTime(2014, 7, 14),
Amount = 1000,
ItemCode = "BBB"
});
secondAccount.Add(new Second()
{
Account = "1234",
Begin = new DateTime(2014, 5, 13),
End = new DateTime(2014, 6, 12),
Amount = 9999
});
secondAccount.Add(new Second()
{
Account = "1234",
Begin = new DateTime(2014, 6, 13),
End = new DateTime(2014, 7, 14),
Amount = 2000
});
var result = from account in (from first in firstAccount
join second in secondAccount
on first.Account equals second.Account
where
((first.Begin >= second.Begin && first.Begin <= second.Begin) &&
(first.End >= second.Begin && first.End <= second.End))
select new
{
first.Account,
second.Begin,
second.End,
first.Amount,
first.ItemCode
})
group account by new {account.Account, account.Begin, account.End }
into groupedAccounts
select new
{
groupedAccounts.Key.Account,
groupedAccounts.Key.Begin,
groupedAccounts.Key.End,
Sum = groupedAccounts.Sum(a => a.Amount)
};
One way to get the itemcode is to change the last select.
Add this line
Itemcode = String.Join(" ",groupedAccounts.Select(q=> q.ItemCode))
after Sum = groupedAccounts.Sum(a => a.Amount),
It should produce itemcode
foreach (var data in result)
{
Console.WriteLine(data.Account + " " + data.Itemcode);
}
Output
1234 AAA
1234 AAA

Categories