Как получить вложенную цепочку объектов в приложениях Linq и MVC2? - PullRequest
1 голос
/ 03 января 2011

Я запутался в том, как решить эту проблему в 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.

Ответы [ 2 ]

1 голос
/ 03 января 2011

Поскольку ответ не был подавляющим :-) Я попытаюсь ответить сам.По моему мнению, это все еще не вполне удовлетворительно, потому что мне нужно создать несколько промежуточных вспомогательных классов для хранения значений во 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; }
    }

И затем у меня есть тот же метод, что и раньше, чтобы получить отфильтрованные сегменты времени:

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

Это лучший способ достичь этого или я все еще слишком усложняю вещи?

0 голосов
/ 03 января 2011

я думаю, что лучший способ решить эту проблему - создать процедуру хранилища, которая будет возвращать все необходимые данные, такие как Project Task TimeSegment Примерно так:

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

Также вы можете использовать LoadWith возможность.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...