Запрос Linq2SQL — Сводные данные с группировкой

#linq-to-sql #group-by #pivot

#linq-to-sql #группировка по #сводные

Вопрос:

У меня есть следующий набор данных для расписания, которое должно отображаться в сетке. В настоящее время фрагмент набора данных выглядит следующим образом:

 SessionNum    TimeStart    TimeStop    Details
----------    ---------    --------    -------
1             08:00        09:00       Math101
1             09:00        10:00       Comp102
1             11:00        12:00       Engn101
2             08:00        09:00       Comp102
2             09:00        10:00       Math101
2             10:00        11:00       Acco103
  

Всего 5 сеансов, и я бы хотел, чтобы набор данных выглядел так:

 TimeStart    TimeStop    Session1    Session2     ...
---------    --------    --------    --------     ---
08:00        09:00       Math101     Comp102
09:00        10:00       Comp102     Math101
10:00        11:00       -           Acco103
11:00        12:00       Engn101     -
  

Как вы увидите, агрегатных функций не требуется…просто группировка, но ни за что на свете я, кажется, не могу разобраться в этом. У меня есть следующий запрос LINQ, который генерирует первый набор данных:

 List<TimeTable> list = db.TimeTables.OrderBy(o => o.TimeStart).OrderBy(o => o.SessionNum).ToList();
  

Это работает нормально и генерирует набор данных, отсортированных по SessionNum и затем TimeStart . Моя попытка решить эту проблему привела к следующему запросу:

 var result = list.GroupBy(t => t.TimeStart).Select(s => new {
    TimeStart = s.Key,
    Session1 = s.Where(x => x.SessionNum == 1),
    Session2 = s.Where(x => x.SessionNum == 2)
});
  

Это запустилось, но, к сожалению, не сработало. Я знаю GroupBy , что требуется / требуется (или пара), но с этого момента я немного потерялся. Я был бы очень признателен за любую помощь в решении этой проблемы. Заранее благодарю вас!

Ответ №1:

Вы не можете напрямую выполнить сводный запрос в LINQ. Вместо этого вы можете создать структуру, подобную этой:

 var record = new
{
    TimeStart = "10:00",
    TimeStop = "11:00",
    Sessions = new [] { "-", "Acco103", },
};
  

Когда у вас есть список этих записей, вы должны убедиться, что Sessions свойство array имеет ту же длину, что и определенное количество сеансов во всем вашем наборе данных. Затем вы можете получить доступ к информации о сеансе путем индексации в массив.

Это должно иметь больше смысла после просмотра запросов.

Сначала запросите необходимые данные в базе данных:

 var query =
    from s in db.TimeTables
    orderby s.TimeStop
    orderby s.TimeStart
    group s by new { s.TimeStart, s.TimeStop } into gss
    select new
    {
        gss.Key.TimeStart,
        gss.Key.TimeStop,
        Sessions = gss.ToArray(),
    };
  

Теперь определите отдельный набор сеансов:

 var sessionNums =
    db.TimeTables
        .Select(s => s.SessionNum)
        .Distinct()
        .OrderBy(n => n)
        .ToArray();
  

Теперь обработайте эти данные в памяти (обратите внимание на .ToArray() вызов on query ):

 var process =
    from q in query.ToArray()
    let lookup = q.Sessions
        .ToLookup(s => s.SessionNum, s => s.Details)
    select new
    {
        q.TimeStart,
        q.TimeStop,
        Sessions = sessionNums
            .Select(n => String.Join(
                ", ",
                lookup[n].DefaultIfEmpty("-")))
            .ToArray(),
    };
  

Вот где тяжелая работа. Это lookup создает простой способ получить подробную информацию о сеансе для любого SessionNum . Вызов lookup[n].DefaultIfEmpty("-") гарантирует наличие хотя бы одного значения для каждого сеанса. Это String.Join гарантирует, что если у исходных данных одновременно было два сеанса для одного и того же номера сеанса, мы получим одно значение.

Этот результат безопасен независимо от количества сеансов, поскольку он просто расширит массивы.

Вывод process запроса выглядит следующим образом:

процесс-дамп

Затем вы можете выполнить этот запрос:

 var result =
    from p in process
    select new
    {
        p.TimeStart,
        p.TimeStop,
        Session1 = p.Sessions[0],
        Session2 = p.Sessions[1],
    };
  

Это эффективно «сводит» ваши результаты, но вам нужно явно указать каждое свойство «SessionX».

Вывод result запроса выглядит следующим образом:

результат -дамп

Комментарии:

1. ВАУ!! Это один очень всеобъемлющий ответ. СПАСИБО, загадочность. Поскольку я вижу это только сейчас, пожалуйста, дайте мне немного времени, чтобы полностью понять это и реализовать. Я скоро отправлю ответ

2. ВАУ! И у нас есть победитель. Ваше решение работает на 100%. Еще раз благодарю ВАС за загадочность. Мне нужно будет посидеть с этим и разобраться глубже, и я думаю, мне еще многое предстоит узнать о LINQ. Однако, что, если количество сеансов было неизвестно? Как был бы написан ваш последний блок кода, поскольку я не думаю, что a foreach() будет работать?

3. Кроме того, я знаю, что вы использовали LINQPad, но в VS2010 мне пришлось изменить: Select(n => String.Join(", ", lookup[n].DefaultIfEmpty("-"))).ToArray() to Select(n => String.Join(", ", lookup[n].DefaultIfEmpty("-").ToArray())) . И Session1 = p.Sessions[0] для Session1 = p.Sessions.ToArray()[0]

4. @maGz — Попробуйте вставить Select(n => String.Join(", ", lookup[n].DefaultIfEmpty("-").ToArray())).ToArray() , и вы сможете .ToArray() использовать каждый Session1 = p.Sessions[0] из них. Если вы не знаете, сколько сеансов, вам нужно будет изменить окончательный запрос вручную, когда вы узнаете. В противном случае просто придерживайтесь process запроса — его не волнует, сколько сеансов существует.

5. привет, и последнее… есть ли какой-либо способ получить дополнительные данные? есть еще один столбец, который я хотел бы объединить с Details called Notes . Дело в том, что я хотел бы управлять выводом HTML из запроса LINQ, но я знаю, что по соображениям безопасности LINQ этого не позволит. Есть ли у вас какой-либо способ, которым это можно сделать?