#c# #asp.net #asp.net-mvc #linq #asp.net-mvc-5
#c# #asp.net #asp.net-mvc #linq #asp.net-mvc-5
Вопрос:
Вот пример данных для моей проблемы,
List<EMPINSURED> lstEmpIns = new List<EMPINSURED>{
new EMPINSURED{ EmpID = 558, TOTAL_INSURED_AMT = 50000},
new EMPINSURED{ EmpID = 559, TOTAL_INSURED_AMT = 75000}
};
List<EMP_ROUTINE_CHECKUP_HISTORY> lstEmpRoutineChkHist = new List<EMP_ROUTINE_CHECKUP_HISTORY>{
new EMP_ROUTINE_CHECKUP_HISTORY{ ID = 1, EmpID = 558, ROUTINE_CHECKUP = 1, CHECK_UP_CHARGE = 20000},
new EMP_ROUTINE_CHECKUP_HISTORY{ ID = 2, EmpID = 559, ROUTINE_CHECKUP = 1, CHECK_UP_CHARGE = 35000},
new EMP_ROUTINE_CHECKUP_HISTORY{ ID = 3, EmpID = 558, ROUTINE_CHECKUP = 2, CHECK_UP_CHARGE = 10000},
new EMP_ROUTINE_CHECKUP_HISTORY{ ID = 4, EmpID = 559, ROUTINE_CHECKUP = 2, CHECK_UP_CHARGE = 10000},
new EMP_ROUTINE_CHECKUP_HISTORY{ ID = 5, EmpID = 558, ROUTINE_CHECKUP = 3, CHECK_UP_CHARGE = 20000}
};
В год сотруднику разрешается только три плановых осмотра. Когда сотрудник проходит плановую проверку, его / ее расходы на проверку должны быть вычтены из общей страховой суммы. При следующем плановом осмотре сотруднику будут начислены расходы на проверку только на оставшуюся у него сумму от общей страховой суммы, какие-либо расходы, превышающие эту сумму, компания нести не будет.
Итак, логика расчета REMAINING AMOUNT
будет примерно такой ::
int var = 0;
if(ROUTINE_CHECKUP == 1)
{
REMAINING_AMOUNT = TOTAL_INSURED_AMT - CHECK_UP_CHARGE;
var = CHECK_UP_CHARGE;
}
else if (ROUTINE_CHECKUP == 2)
{
REMAINING_AMOUNT = TOTAL_INSURED_AMT - (CHECK_UP_CHARGE var);
var = CHECK_UP_CHARGE var;
}
else if (ROUTINE_CHECKUP == 3)
{
REMAINING_AMOUNT = TOTAL_INSURED_AMT - (CHECK_UP_CHARGE var);
var = CHECK_UP_CHARGE var;
}
Как разработать запрос LINQ, чтобы получить приведенные ниже данные отчета, используя эту логику, или может быть какой-то лучший способ сделать это.
Пожалуйста, смотрите прикрепленное изображение для формата отчета.
Комментарии:
1. Как насчет сотрудников, которые еще не прошли проверку? Вы хотите, чтобы они также были в списке с
RoutineChekup = 0, CheckupCharge=0, RemainingAmount = TotalInsuredAmount
?2. Да, я также должен их показать.
Ответ №1:
Используя некоторые System.Linq
методы расширения, вы можете Join
преобразовать lstEmpIns
список с lstEmpRoutineChkHist
списком в EmpId
поле в анонимный тип, который имеет значения для столбцов из соответствующих полей в каждом списке, и упорядочить результаты с помощью RoutineCheckup
использования OrderBy
.
Неприятная часть заключается в определении RemainingBalance
, потому что мы должны искать все записи для этого сотрудника, где RoutineCheckup
меньше или равно текущему элементу, а затем суммировать CheckupCharge
, чтобы мы могли вычесть его из RemainingBalance
. Вероятно, есть лучший способ сделать это, но это то, что я придумал.
Вот пример, основанный на вашем коде, но использующий современные соглашения об именовании:
var results = empInsureds
.Join(empRoutineCheckupHistories,
emp => emp.EmpId,
hist => hist.EmpId,
(emp, hist) => new
{
emp.EmpId,
emp.TotalInsuredAmt,
hist.RoutineCheckup,
hist.CheckupCharge,
RemainingBalance =
emp.TotalInsuredAmt -
empRoutineCheckupHistories
.Where(h => h.EmpId == hist.EmpId amp;amp;
h.RoutineCheckup <= hist.RoutineCheckup)
.Sum(h => h.CheckupCharge)
})
.OrderBy(r => r.RoutineCheckup)
.ToList();
Некоторый код для генерации выходных данных:
Console.WindowWidth = 81;
Console.WriteLine(GetRow("EmpID", "Total Insured Amt", "Routine Checkup",
"Checkup Charge", "Remaining Amount"));
Console.WriteLine(new string('-', 80));
results.ForEach(r => Console.WriteLine(GetRow($"{r.EmpId}", $"{r.TotalInsuredAmt:C}",
$"{r.RoutineCheckup}", $"{r.CheckupCharge:C}", $"{r.RemainingBalance:C}")));
GetKeyFromUser("nDone! Press any key to exit...");
Которая использует вспомогательную функцию:
public static string GetRow(string col1, string col2, string col3, string col4,
string col5)
{
return col1.PadRight(5) " | "
col2.PadLeft(17) " | "
col3.PadRight(16) " | "
col4.PadLeft(14) " | "
col5.PadLeft(16);
}
Вывод
Комментарии:
1. Могу ли я использовать
group
предложение с этим для group byEmpID, RoutineCheckup
?
Ответ №2:
Моим советом было бы использовать одну из перегрузок запрашиваемых.GroupJoin или его эквивалент IEnumerable, если ваши источники не IQueryable, но IEnumerable.
С помощью GroupJoin вы можете получить «Всех сотрудников, каждого со всеми его нулевыми или более плановыми проверками». Используя параметр resultSelector, вы можете создать желаемый результат.
Преимущество группового объединения в том, что вы также получите сотрудников, которые до сих пор не проходили проверку. Если бы вы использовали объединение, вы бы вообще не получили этих сотрудников.
var employeesWithTheirCheckups = dbContext.Employees.GroupJoin(
dbContext.RoutineCheckUps,
employee => employee.EmpId, // from every Employee take the primary key
checkup => checkup.EmpId, // from every RoutineCheckup take the foreign key
// to the Employee
// parameter resultSelector: for every Employee and his zero or more Checkups,
// make one new object:
(employee, checkupsOfThisEmployee) => new
{
EmpId = employee.Id,
TotalInsuredAmount = employee.TotalInsuredAmount,
Checkups = checkupsOfThisEmployee.Select(checkup => new
{
RoutineCheckup = checkup.RoutineCheckup,
CheckupCharge = checkup.CheckupCharge,
// TODO: Remaining Amount
})
.ToList(),
});
Оставшиеся расходы на осмотр равны общей сумме этого сотрудника за вычетом «общих расходов на осмотр этого сотрудника до этого осмотра».
Итак, для истории с RoutineCheckup 2 вам нужно сложить все начисленные суммы всех историй с RoutineCheckups <= 2.
Это рассчитывается следующим образом:
var chargedUntilNow = checkupsOfThisEmployee
.Where(checkupOfThisEmployee => checkupOfThisEmployee <= checkup)
.Select(checkupOfThisEmployee => checkupOfThisEmployee.CheckUpCharge)
.Sum();
Оставшаяся сумма — это сумма, начисленная этому сотруднику до настоящего времени.
Вернуться к групповому соединению:
(employee, checkupsOfThisEmployee) => new
{
EmpId = employee.Id,
TotalInsuredAmount = employee.TotalInsuredAmount,
Checkups = checkupsOfThisEmployee.Select(checkup => new
{
RoutineCheckup = checkup.RoutineCheckup,
CheckupCharge = checkup.CheckupCharge,
// remaining is the total of this employee - chargedUntilNow.
RemainingAmount = employee.TotalInsured -
checkupsOfThisEmployee
.Where(checkupOfThisEmployee => checkupOfThisEmployee <= checkup)
.Select(checkupOfThisEmployee => checkupOfThisEmployee.CheckUpCharge)
.Sum();
})
.ToList(),
Результатом является последовательность всех сотрудников (ну, только его идентификатор и его общая страховая сумма), каждый сотрудник с нулевыми или более проверками. Каждая проверка будет иметь номер обычной проверки, количество проверок и оставшееся количество.
Хорошая вещь: у вас также будут сотрудники, у которых еще не было проверки!
Кроме того: [EmpID, TotalInsuredAmount], будет переведен только один раз для каждого сотрудника. Если бы вы использовали стандартное объединение, то один и тот же [EmpID, TotalInsuredAmount] отправлялся бы один раз за проверку.
Если вы хотите, чтобы она была плоской, как ваш конечный результат, используйте SelectMany. Продолжение LINQ:
.SelectMany(groupJoinResult, groupJoinResult.Checkups,
// parameter resultSelector: take the GroupJoinResult, and each one of its Checkups
// to make one new:
(groupJoinResult, checkupOfThisGroupJoinResult) => new
{
EmpId = groupJoinResult.EmpId,
TotalInsuredAmount = groupJoinResult.TotalInsuredAmount,
RoutineCheckUp = checkupOfThisGroupJoinResult.RoutineCheckup,
CheckupCharge = checkupOfThisGroupJoinResult.CheckupCharge,
RemainingAmount = checkupOfThisGroupJoinResult.RemainingAmount,
});
Проблема в том, что вы потеряете сотрудника, у которого еще не было проверок. Чтобы решить эту проблему, вам нужно будет указать значение по умолчанию