我基本上只是想与here做相同的事情,只是我想用C#而不是SQL来做。
我有一堂课
public class AmountPerPeriod
{
public int Id { get; set; }
public DateTime Startdate { get; set; }
public DateTime Enddate { get; set; }
public decimal Amount { get; set; }
}
在此示例中,我用以下内容填充了AmountPerPeriod
个项目的列表:
var lstAmountPerPeriod = new List<AmountPerPeriod>()
{
new AmountPerPeriod
{
Id = 1,
Startdate = new DateTime(2019, 03, 21),
Enddate = new DateTime(2019, 05, 09),
Amount = 10000
},
new AmountPerPeriod
{
Id = 2,
Startdate = new DateTime(2019, 04, 02),
Enddate = new DateTime(2019, 04, 10),
Amount = 30000
},
new AmountPerPeriod
{
Id = 3,
Startdate = new DateTime(2018, 11, 01),
Enddate = new DateTime(2019, 01, 08),
Amount = 20000
}
};
我希望我的输出成为如下所示的AmountPerMonth类的列表:
public class AmountPerMonth
{
public int Id { get; set; }
public int Year { get; set; }
public int Month { get; set; }
public decimal Amount { get; set; }
}
就像我应该尝试的那样,我得到了一种有效的工作方法,我觉得它变得复杂了。提供正确结果的这种方法如下所示:
var result = new List<AmountPerMonth>();
foreach (var item in lstAmountPerPeriod)
{
if (item.Startdate.Year == item.Enddate.Year && item.Startdate.Month == item.Enddate.Month)
{
result.Add(new AmountPerMonth
{
Amount = item.Amount,
Id = item.Id,
Month = item.Startdate.Month,
Year = item.Startdate.Year
});
}
else
{
var numberOfDaysInPeriod = (item.Enddate - item.Startdate).Days+1;
var amountPerDay = item.Amount / numberOfDaysInPeriod;
var periodStartDate = item.Startdate;
bool firstPeriod = true;
while (periodStartDate.ToFirstDateOfMonth() <= item.Enddate.ToFirstDateOfMonth())
{
if (firstPeriod)
{
result.Add(new AmountPerMonth
{
Amount = ((periodStartDate.ToLastDateOfMonth()-periodStartDate).Days+1)*amountPerDay,
Id = item.Id,
Month = periodStartDate.Month,
Year = periodStartDate.Year
});
}
else if (periodStartDate.Month != item.Enddate.Month)
{
result.Add(new AmountPerMonth
{
Amount = ((periodStartDate.ToLastDateOfMonth()-periodStartDate.ToFirstDateOfMonth()).Days+1) * amountPerDay,
Id = item.Id,
Month = periodStartDate.Month,
Year = periodStartDate.Year
});
}
else
{
result.Add(new AmountPerMonth
{
Amount = ((item.Enddate - periodStartDate.ToFirstDateOfMonth()).Days+1) * amountPerDay,
Id = item.Id,
Month = periodStartDate.Month,
Year = periodStartDate.Year
});
}
periodStartDate = periodStartDate.AddMonths(1);
firstPeriod = false;
}
}
}
// assert using fluentassertions
result.Count.Should().Be(7);
result.First().Amount.Should().Be(2200);
result.Last().Amount.Should().BeApproximately(2318.84M, 2);
// list with result basically should contain:
// ID |month |year |amount
// ---|------|-------|--------
// 1 |3 | 2019 | 2200.00
// 1 |4 | 2019 | 6000.00
// 1 |5 | 2019 | 1800.00
// 2 |4 | 2019 |30000.00
// 3 |11 | 2018 | 8695.65
// 3 |12 | 2018 | 8985.51
// 3 |1 | 2019 | 2318.84
就像我说的那样,应该有一种更简单的方法,甚至可以使用LINQ。有人有建议吗?
预先感谢
答案 0 :(得分:2)
使用LINQ更加容易。
var output =
from app in lstAmountPerPeriod
let days = (int)app.Enddate.Date.Subtract(app.Startdate.Date).TotalDays + 1
from day in Enumerable.Range(0, days)
let daily = new AmountPerPeriod()
{
Id = app.Id,
Startdate = app.Startdate.AddDays(day),
Enddate = app.Startdate.AddDays(day),
Amount = app.Amount / days
}
group daily.Amount by new
{
daily.Id,
daily.Startdate.Year,
daily.Startdate.Month
} into gds
select new AmountPerMonth()
{
Id = gds.Key.Id,
Year = gds.Key.Year,
Month = gds.Key.Month,
Amount = gds.Sum(),
};
此查询返回:
答案 1 :(得分:1)
这是另一种方式,这里的基本区别是我使用for
循环将“第一天”连续更新为当前时间段的第一天或第一天一个月,以及“最后一天”到当月的最后一天或时段的最后一天,以较小者为准。
我还在AmountPerMonth
类上将此方法添加为静态方法,该方法接受AmountPerPeriod
并返回List<AmountPerMonth>
。另外,我覆盖了ToString
方法,以输出与您的问题类似的字符串,因此输出看起来相同:
public class AmountPerMonth
{
public int Id { get; set; }
public int Year { get; set; }
public int Month { get; set; }
public decimal Amount { get; set; }
public static List<AmountPerMonth> FromPeriod(AmountPerPeriod period)
{
if (period == null) return null;
var amtPerDay = period.Amount / ((period.EndDate - period.StartDate).Days + 1);
var result = new List<AmountPerMonth>();
for (var date = period.StartDate; date <= period.EndDate;
date = date.AddMonths(1).ToFirstDateOfMonth())
{
var lastDayOfMonth = date.ToLastDateOfMonth();
var lastDay = period.EndDate < lastDayOfMonth
? period.EndDate
: lastDayOfMonth;
var amount = ((lastDay - date).Days + 1) * amtPerDay;
result.Add(new AmountPerMonth
{
Id = period.Id,
Year = date.Year,
Month = date.Month,
Amount = amount
});
}
return result;
}
public override string ToString()
{
return $"{Id,-3} |{Month,-6}| {Year,-6}| {Amount:0.00}";
}
}
我们可以使用此方法作为示例数据中SelectMany
的参数,以生成我们的列表并输出结果:
static void Main(string[] args)
{
var lstAmountPerPeriod = new List<AmountPerPeriod>()
{
new AmountPerPeriod
{
Id = 1,
StartDate = new DateTime(2019, 03, 21),
EndDate = new DateTime(2019, 05, 09),
Amount = 10000
},
new AmountPerPeriod
{
Id = 2,
StartDate = new DateTime(2019, 04, 02),
EndDate = new DateTime(2019, 04, 10),
Amount = 30000
},
new AmountPerPeriod
{
Id = 3,
StartDate = new DateTime(2018, 11, 01),
EndDate = new DateTime(2019, 01, 08),
Amount = 20000
}
};
var amountsPerMonth = lstAmountPerPeriod.SelectMany(AmountPerMonth.FromPeriod);
Console.WriteLine("ID |month |year |amount");
Console.WriteLine("---|------|-------|--------");
Console.WriteLine(string.Join(Environment.NewLine, amountsPerMonth));
GetKeyFromUser("\n\nDone! Press any key to exit...");
}
输出
注意:上面的代码中使用了这些扩展方法:
public static class Extensions
{
public static DateTime ToFirstDateOfMonth(this DateTime input)
{
return new DateTime(input.Year, input.Month, 1, input.Hour,
input.Minute, input.Second, input.Millisecond, input.Kind);
}
public static DateTime ToLastDateOfMonth(this DateTime input)
{
return new DateTime(input.Year, input.Month,
DateTime.DaysInMonth(input.Year, input.Month), input.Hour,
input.Minute, input.Second, input.Millisecond, input.Kind);
}
}