#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()
toSelect(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
calledNotes
. Дело в том, что я хотел бы управлять выводом HTML из запроса LINQ, но я знаю, что по соображениям безопасности LINQ этого не позволит. Есть ли у вас какой-либо способ, которым это можно сделать?