我对如何在Linq中解决这个问题感到困惑。我有一个有效的解决方案,但我认为执行它的代码太复杂和循环了:
我在MVC 2中有一个时间表应用程序。我想查询具有以下表格的数据库(简化):
项目 任务 TimeSegment
关系如下:项目可以有很多任务,任务可以有很多时间段。
我需要能够以不同的方式查询它。例如:View是一个报告,它将显示表格中的项目列表。将列出每个项目的任务,然后列出该任务的工作小时数。 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()方法的组实现了这一点(如果扩展为具有date的条件等等),但是我不能返回它并将它传递给视图,因为它是一个匿名对象。我也试过在这样的查询中创建一个特定的类型,但是无法绕过那个......
我希望有一些我缺少的东西,并且有一些更简单的方法来实现这一点,因为我最终还需要能够做其他几个不同的查询。
我也不太喜欢用“CurrentTimeSegments”属性解决它的方式等等。这些属性首先不存在于模型对象中,我将它们添加到部分类中,以便为嵌套对象链的每个部分放置过滤结果......
有什么想法吗?
更新:
我想我正在寻找这些方面的东西:How do I create a nested group-by dictionary using LINQ?。如果它是这样的话,我真的很感激帮助将它转化为我的问题,因为我试过但失败了。但是我可能不会采取这种策略(说实话,它似乎比我预期的这种查询更复杂),如果是这样,请告诉我!
更新2:关于上面的链接,这似乎也不是正确的,因为当我尝试Jon Skeet的例子(字面意思,没有翻译成我的问题)我没有得到他指定的字典类型根本......编译器说它无法转换它。我需要返回一个特定的类型,因为这个结果必须作为ViewModel传递给View。
答案 0 :(得分:1)
由于反应并不是压倒性的:-)我会自己尝试一个答案。在我看来,它仍然不太令人满意,因为我必须创建一堆中间辅助类来保存ViewModel中的值。所以如果有人能告诉我一个更好的方法,我会很乐意接受另一个答案。
无论如何,我所做的是创建了一个带有几个类的ViewModel来保存过滤后的对象:
public class MonthlyReportViewModel
{
public List<CurrentProject> Projects { get; set; }
public string Employee { get; set; }
public Customer Customer { get; set; }
public int TotalHours { get; set; }
public int TotalCost { get; set; }
public double TotalCostAndVAT { get; set; }
public double VAT { get; set; }
public string Month { get; set; }
public string StartDate { get; set; }
public string EndDate { get; set; }
}
public class CurrentProject
{
public string Name { get; set; }
public List<CurrentTask> CurrentTasks { get; set; }
}
public class CurrentTask
{
public string Name { get; set; }
public List<TimeSegment> CurrentTimeSegments { get; set; }
public int Cost { get; set; }
public int Hours { get; set; }
public int Fee { get; set; }
}
然后我使用与之前相同的方法来获取过滤的TimeSegments:
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();
}
最后我创建了一个新的GetProjects()方法,根据过滤的时间段对过滤后的项目进行分组和返回:
public List<CurrentProject> GetProjects(List<TimeSegment> timeSegments)
{
IEnumerable<CurrentProject> currentProjects = from timeSegment in timeSegments
group timeSegment by timeSegment.Task.Project
into projectTimeSegments
select new CurrentProject()
{
Name = projectTimeSegments.Key.ProjectName,
CurrentTasks = (from projectTimeSegment in projectTimeSegments
group projectTimeSegment by projectTimeSegment.Task
into taskTimeSegments
let fee = taskTimeSegments.Key.Fee
let hours = taskTimeSegments.Sum(t=>t.Hours)
select new CurrentTask
{
Name = taskTimeSegments.Key.TaskName,
Fee = fee,
Cost = hours*fee,
Hours = hours,
CurrentTimeSegments =
taskTimeSegments.ToList()
}).ToList()
};
return currentProjects.ToList();
}
这是实现这一目标的最佳方式还是我仍然过度复杂化?
答案 1 :(得分:0)
我认为解决此问题的最佳方法是创建存储过程,该过程将返回所有需要的数据,例如Project Task TimeSegment。像这样:
select * from Projects where ...
select * from Tasks where ...
select * from TimeSegment where ...
您也可以使用LoadWith机会。