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

发布于 2024-10-10 11:57:51 字数 7904 浏览 2 评论 0原文

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

再次,正如我提到的,这是可行的,但当然非常复杂,即使现在我正在编码它,我自己也会感到困惑。我觉得一定有一种更简单的方法来实现我想要的。基本上你可以从视图中看出我想要实现什么:

我想要获得项目的集合。每个项目都应该有其关联的任务集合。每个任务都应该有指定日期期间的关联时间段集合。请注意,所选的项目和任务也只能是具有该时间段的项目和任务。我不想要在此期间内没有时间段的所有项目和任务。

似乎以 GetProjects() 方法开头的 group by Linq 查询实现了这一点(如果扩展为具有日期等条件),但我无法返回它并将其传递给视图,因为它是匿名的目的。我还尝试在这样的查询中创建特定类型,但也无法解决这个问题...

我希望我缺少一些东西,并且有一些更简单的方法可以实现这一点,因为我需要能够最终也可以进行其他几个不同的查询。

我也不太喜欢使用“CurrentTimeSegments”属性等解决问题的方式。这些属性首先并不真正存在于模型对象上,我将它们添加到部分类中,以便有地方放置嵌套对象链每个部分的过滤结果......

有什么想法吗?

更新:

认为我正在寻找以下内容:如何是否使用 LINQ 创建嵌套分组字典?。如果是这样的,我真的很感激一些帮助将其转化为我的问题,因为我尝试过但失败了。但我可能不喜欢这个策略(说实话,它似乎比我预期的这种查询更复杂),如果是这样,请告诉我!

更新2:关于上面的链接,这似乎也不太正确,因为当我尝试 Jon Skeet 的示例(字面意思是,没有翻译成我的问题)时,我根本没有得到他指定的字典类型。 ..编译器说它无法转换它。我需要返回一个特定的类型,因为这个结果必须作为 ViewModel 传递给 View。

I am getting all confused about how to solve this problem in Linq. I have a working solution, but the code to do it is way too complicated and circular I think:

I have a timesheet application in MVC 2. I want to query the database that has the following tables (simplified):

Project
Task
TimeSegment

The relationships are as follows: A project can have many tasks and a task can have many timesegments.

I need to be able to query this in different ways. An example is this: A View is a report that will show a list of projects in a table. Each project's tasks will be listed followed by a Sum of the number of hours worked on that task. The timesegment object is what holds the hours.

Here's the View:

<%@ 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>

Here's the action method:

    [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);
    }

And the "helper" methods:

    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;
    }

Again, as I mentioned, this works, but of course is really complex and I get confused myself just looking at it even now that I'm coding it. I sense there must be a much easier way to achieve what I want. Basically you can tell from the View what I want to achieve:

I want to get a collection of projects. Each project should have it's associated collection of tasks. And each task should have it's associated collection of timesegments for the specified date period. Note that the projects and tasks selected must also only be the projects and tasks that have the timesegments for this period. I don't want all projects and tasks that have no timesegments within this period.

It seems the group by Linq query beginning the GetProjects() method sort of achieves this (if extended to have the conditions for date and so on), but I can't return this and pass it to the view, because it is an anonymous object. I also tried creating a specific type in such a query, but couldn't wrap my head around that either...

I hope there is something I'm missing and there is some easier way to achieve this, because I need to be able to do several other different queries as well eventually.

I also don't really like the way I solved it with the "CurrentTimeSegments" properties and so on. These properties don't really exist on the model objects in the first place, I added them in partial classes to have somewhere to put the filtered results for each part of the nested object chain...

Any ideas?

UPDATE:

I think I'm looking for something along these lines: How do I create a nested group-by dictionary using LINQ?. If it is something like this, I'd really appreciate some help translating it into my problem, because I tried but failed. But I may be off on this strategy (to be honest it too seems more complex than I would have expected this sort of query to be), and if so, please tell me!

UPDATE 2: Regarding the link above, that doesn't seem to be quite right either, because when I try the example from Jon Skeet (literally, not translated to my problem) I don't get the Dictionary type he specifies at all... The compiler says it can't convert it. And I need to have a specific type to return, since this result must be passed to the View as a ViewModel.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

铁轨上的流浪者 2024-10-17 11:57:51

由于反应并不是压倒性的:-)我会尝试自己回答。在我看来,这仍然不太令人满意,因为我必须创建一堆中间帮助器类来保存 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();
    }

这是实现此目标的最佳方法还是我仍然使事情过于复杂?

Since the response hasn't been overwhelming :-) I'll attempt an answer myself. It's still not quite satisfactory in my own opinion, because I have to create a bunch of intermediate helper classes to hold the values in the ViewModel. So I'd be happy to accept another answer, if anyone can show me a better way to do it.

Anyway, what I have done is I created a ViewModel with a few classes to hold the filtered objects:

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; }
    }

And then I have the same method as before to get the filtered 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();
    }

And finally I created a new GetProjects() method to group and return filtered projects based on the filtered timesegments:

    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();
    }

Is this the best way to achieve this or am I still overcomplicating things?

漫漫岁月 2024-10-17 11:57:51

我认为解决此问题的最佳方法是创建存储过程,它将返回所有需要的数据,例如项目任务时间段。像这样的事情:

select * from Projects where ...
select * from Tasks where ...
select * from TimeSegment where ...

您也可以使用 LoadWith 机会。

i think that best way for resolve this is create store procedure that will return all needed data such as Project Task TimeSegment. Something like this :

select * from Projects where ...
select * from Tasks where ...
select * from TimeSegment where ...

Also you can use LoadWith opportunity.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文