如何在Linq和MVC2应用程序中获得嵌套的对象链?

时间:2011-01-02 22:49:30

标签: asp.net linq asp.net-mvc-2

我对如何在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。

2 个答案:

答案 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机会。