Я запутался в том, как решить эту проблему в Linq. У меня есть рабочее решение, но я думаю, что этот код слишком сложный и циклический:
У меня есть приложение расписания в MVC 2. Я хочу запросить базу данных, которая имеет следующие таблицы (упрощенно):
Проект
задача
TimeSegment
Отношения таковы: у проекта может быть много задач, а у задачи может быть много временных сегментов.
Мне нужно иметь возможность запрашивать это по-разному. Пример таков: представление - это отчет, в котором будет показан список проектов в таблице. Будут перечислены задачи каждого проекта, а затем сумма часов, потраченных на выполнение этой задачи. Объект временного сегмента - это то, что содержит часы.
Вот вид:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Report.Master" Inherits="System.Web.Mvc.ViewPage<Tidrapportering.ViewModels.MonthlyReportViewModel>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Månadsrapport
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h1>
Månadsrapport</h1>
<div style="margin-top: 20px;">
<span style="font-weight: bold">Kund: </span>
<%: Model.Customer.CustomerName %>
</div>
<div style="margin-bottom: 20px">
<span style="font-weight: bold">Period: </span>
<%: Model.StartDate %> - <%: Model.EndDate %>
</div>
<div style="margin-bottom: 20px">
<span style="font-weight: bold">Underlag för: </span>
<%: Model.Employee %>
</div>
<table class="mainTable">
<tr>
<th style="width: 25%">
Projekt
</th>
<th>
Specifikation
</th>
</tr>
<% foreach (var project in Model.Projects)
{
%>
<tr>
<td style="vertical-align: top; padding-top: 10pt; width: 25%">
<%:project.ProjectName %>
</td>
<td>
<table class="detailsTable">
<tr>
<th>
Aktivitet
</th>
<th>
Timmar
</th>
<th>
Ex moms
</th>
</tr>
<% foreach (var task in project.CurrentTasks)
{%>
<tr class="taskrow">
<td class="task" style="width: 40%">
<%: task.TaskName %>
</td>
<td style="width: 30%">
<%: task.TaskHours.ToString()%>
</td>
<td style="width: 30%">
<%: String.Format("{0:C}", task.Cost)%>
</td>
</tr>
<% } %>
</table>
</td>
</tr>
<% } %>
</table>
<table class="summaryTable">
<tr>
<td style="width: 25%">
</td>
<td>
<table style="width: 100%">
<tr>
<td style="width: 40%">
Totalt:
</td>
<td style="width: 30%">
<%: Model.TotalHours.ToString() %>
</td>
<td style="width: 30%">
<%: String.Format("{0:C}", Model.TotalCost)%>
</td>
</tr>
</table>
</td>
</tr>
</table>
<div class="price">
<table>
<tr>
<td>Moms: </td>
<td style="padding-left: 15px;">
<%: String.Format("{0:C}", Model.VAT)%>
</td>
</tr>
<tr>
<td>Att betala: </td>
<td style="padding-left: 15px;">
<%: String.Format("{0:C}", Model.TotalCostAndVAT)%>
</td>
</tr>
</table>
</div>
</asp:Content>
Вот метод действия:
[HttpPost]
public ActionResult MonthlyReports(FormCollection collection)
{
MonthlyReportViewModel vm = new MonthlyReportViewModel();
vm.StartDate = collection["StartDate"];
vm.EndDate = collection["EndDate"];
int customerId = Int32.Parse(collection["Customers"]);
List<TimeSegment> allTimeSegments = GetTimeSegments(customerId, vm.StartDate, vm.EndDate);
vm.Projects = GetProjects(allTimeSegments);
vm.Employee = "Alla";
vm.Customer = _repository.GetCustomer(customerId);
vm.TotalCost = vm.Projects.SelectMany(project => project.CurrentTasks).Sum(task => task.Cost); //Corresponds to above foreach
vm.TotalHours = vm.Projects.SelectMany(project => project.CurrentTasks).Sum(task => task.TaskHours);
vm.TotalCostAndVAT = vm.TotalCost * 1.25;
vm.VAT = vm.TotalCost * 0.25;
return View("MonthlyReport", vm);
}
И "вспомогательные" методы:
public List<TimeSegment> GetTimeSegments(int customerId, string startdate, string enddate)
{
var timeSegments = _repository.TimeSegments
.Where(timeSegment => timeSegment.Customer.CustomerId == customerId)
.Where(timeSegment => timeSegment.DateObject.Date >= DateTime.Parse(startdate) &&
timeSegment.DateObject.Date <= DateTime.Parse(enddate));
return timeSegments.ToList();
}
public List<Project> GetProjects(List<TimeSegment> timeSegments)
{
var projectGroups = from timeSegment in timeSegments
group timeSegment by timeSegment.Task
into g
group g by g.Key.Project
into pg
select new
{
Project = pg.Key,
Tasks = pg.Key.Tasks
};
List<Project> projectList = new List<Project>();
foreach (var group in projectGroups)
{
Project p = group.Project;
foreach (var task in p.Tasks)
{
task.CurrentTimeSegments = timeSegments.Where(ts => ts.TaskId == task.TaskId).ToList();
p.CurrentTasks.Add(task);
}
projectList.Add(p);
}
return projectList;
}
Опять же, как я уже говорил, это работает, но, конечно, это действительно сложно, и я запутываюсь, просто смотря на это даже сейчас, когда я его кодирую. Я чувствую, что должен быть намного более простой способ достичь того, чего я хочу. По сути, вы можете сказать из View, чего я хочу достичь:
Я хочу получить коллекцию проектов. С каждым проектом должен быть связан набор задач. И с каждой задачей должен быть связан набор временных сегментов за указанный период. Обратите внимание, что выбранные проекты и задачи также должны быть только теми проектами и задачами, которые имеют временные сегменты для этого периода. Я не хочу, чтобы все проекты и задачи не имели временных сегментов в течение этого периода.
Похоже, что группировка по запросу Linq, начинающая метод GetProjects (), в некотором роде достигает этого (если расширена, чтобы иметь условия для даты и т. Д.), Но я не могу вернуть это и передать его в представление, потому что оно является анонимным объектом Я также пытался создать конкретный тип в таком запросе, но не мог обернуться и вокруг этого ...
Надеюсь, что-то мне не хватает, и есть более простой способ добиться этого, потому что в конечном итоге мне нужно будет выполнять и несколько других запросов.
Мне также не очень нравится, как я решил это с помощью свойств "CurrentTimeSegments" и так далее. Эти свойства на самом деле не существуют в объектах модели, во-первых, я добавил их в частичные классы, чтобы иметь место для размещения отфильтрованных результатов для каждой части цепочки вложенных объектов ...
Есть идеи?
ОБНОВЛЕНИЕ:
Я думаю Я ищу что-то в этом роде: Как создать вложенный словарь группирования с помощью LINQ? . Если это что-то вроде этого, я был бы очень признателен за помощь в переводе этого в мою проблему, потому что я пытался, но потерпел неудачу. Но я могу отказаться от этой стратегии (если честно, она кажется более сложной, чем я ожидал), и если да, скажите мне!
ОБНОВЛЕНИЕ 2: Что касается ссылки выше, это тоже не совсем правильно, потому что, когда я пытаюсь привести пример от Джона Скита (буквально, не переведенный в мою проблему), я не получаю тип словаря, который он указывает вообще ... Компилятор говорит, что не может его конвертировать. И мне нужно иметь определенный тип для возврата, так как этот результат должен быть передан в View как ViewModel.