Linq-to-SQL: как сформировать данные с помощью группировки - PullRequest
4 голосов
/ 20 марта 2010

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

CREATE TABLE [dbo].[Credits] (
    [Id] [int] IDENTITY (1, 1) NOT NULL PRIMARY KEY,
    [PersonId]  [int] NOT NULL FOREIGN KEY REFERENCES People(Id),
    [MovieId]  [int] NOT NULL  FOREIGN KEY REFERENCES Movies(Id),
    [Role]  [char] (1) NULL

В этом простом примере столбец [Роль] представляет собой один символ, по моему соглашению либо «А», чтобы указать, что человек был субъект в этом конкретном фильме или 'D' для режиссер .

Я хотел бы выполнить запрос для конкретного человека, который возвращает имя человека, а также список всех фильмовчеловек работал над ролями в этих фильмах.

Если бы я сериализовал его в json, он мог бы выглядеть так:

{
  "name" : "Clint Eastwood",
  "movies" : [
     { "title": "Unforgiven",        "roles": ["actor", "director"] },
     { "title": "Sands of Iwo Jima", "roles": ["director"] },
     { "title": "Dirty Harry",       "roles": ["actor"] },
     ...
  ]
}

Как я могу написать запрос LINQ-to-SQL, который формирует вывод таким образом?

У меня проблемы с эффективностью.


Попробуйте # 1

, если я использую этот запрос:

  int personId = 10007;
  var persons =
      from p in db.People
      where p.Id == personId
      select new
      {
          name   = p.Name,
          movies =
                (from m in db.Movies
                 join c in db.Credits on m.Id equals c.MovieId
                 where (c.PersonId == personId)
                 select new {
                         title = m.Title,
                         role = (c.Role=="D"?"director":"actor")
                 })
      };

Я получаю что-то вроде этого:

{
  "name" : "Clint Eastwood",
  "movies" : [
     { "title": "Unforgiven",        "role": "actor" },
     { "title": "Unforgiven",        "role": "director" },
     { "title": "Sands of Iwo Jima", "role": "director" },
     { "title": "Dirty Harry",       "role": "actor" },
     ...
  ]
}

Это не такСовершенно верно.Как вы можете видеть, есть дубликат каждого фильма, для которого Иствуд сыграл несколько ролей.Я ожидал бы этого, потому что в таблице кредитов есть несколько строк для этой комбинации фильм + человек, по одной для каждой роли.


Попробуйте # 2

Я думал, что я буду использовать группу по , вот так:

  var persons =
      from p in db.People
      where p.Id == personId
      select new
      {
          name   = p.Name,
          movies =
                (from m in db.Movies
                 join c in db.Credits  on m.Id equals c.MovieId
                 where (c.PersonId == personId)
                 orderby m.Year
                 group ((c.Role == "A")? "actor":"director")
                 by m.Id
                 into g
                 select new {roles = g })
      };

Вывод довольноблизко к тому, что я хочу.Это выглядит так:

{
  "name" : "Clint Eastwood",
  "movies" : [
     { "roles": ["actor", "director"]}, 
     { "roles": ["director"]}, 
     { "roles": ["actor"]},
     ...
  ]
}

Это близко, но, конечно, у меня нет названий фильмов.


Попробуйте # 3

Если я использую группу по и добавлю название фильма, например:

  var persons =
      from p in db.People
      where p.Id == personId
      select new
      {
          name   = p.Name,
          movies =
                (from m in db.Movies
                 join c in db.Credits  on m.Id equals c.MovieId
                 where (c.PersonId == personId)
                 orderby m.Year
                 group ((c.Role == "A")? "actor":"director")
                 by m.Id
                 into g
                 select new { title = m.Title, roles = g })
      };

...то не скомпилируется из-за

ошибки CS0103: имя 'm' не существует в текущем контексте


Как мне сформироватьвыход так, как я хочу?

Ответы [ 3 ]

2 голосов
/ 20 марта 2010

Намного легче рассуждать, если вы начинаете с таблицы отношений (кредитов):

var query =
    from c in context.Credits
    where c.PersonId == 1
    group c by c.Person into g
    select new
    {
        PersonName = g.Key.Name,
        Credits = from cr in g
                  group cr by cr.Movie into g2
                  select new
                  {
                      MovieTitle = g2.Key.Name,
                      Roles = g2.Select(ci =>
                          (ci.Role == 'A') ? "Actor" : "Director")
                  }
    };

Вот код, который будет отображать результаты:

foreach (var result in query)
{
    Console.WriteLine(result.PersonName);
    foreach (var credit in result.Credits)
    {
        string roles = string.Join(",", credit.Roles.ToArray());
        Console.WriteLine("  " + credit.MovieTitle + ": " + roles);
    }
}
1 голос
/ 20 марта 2010

Я считаю, что вам нужно материализовать запрос, затем группировать по имени и названию и использовать string.Join для сопоставления ролей.

  int personId = 10007;
  var persons = db.People.Where( p => p.Id == personId );
  var movies = db.Movies
                 .Join( db.Credits.Where( c => c.PersonId == personId),
                        m => m.Id,
                        c => c.MovieId,
                       (m,c) => new {
                   personid = c.PersonId,
                   title = m.title,
                   role = c.Role == "D" : "director", "actor"
                  })
                 .GroupBy( g => new { g.personid, g.title } )
                 .ToList()
                 .Select( g => new {
                     personid = g.Key.personid,
                     title = g.Key.title
                     roles = string.Join( ",", g.Select( g => g.role ).ToArray() )
                  });

  var personsWithMovies = people.Join( movies, p => p.PersonId, m => m.personid, (p,m) => new {
                            name = p.Name,
                            movies = m 
                          });
0 голосов
/ 20 марта 2010

Благодаря подсказке от tvanfosson я смог придумать это, что мне подходит!

var persons =
     from p in db.People
     where p.Id == personId
     select new
     {
         name   = p.Name,
         movies =
               (from m in db.Movies
                join c in db.Credits on m.Id equals c.MovieId
                where (c.PersonId == personId)
                group ((c.Role =="A")?"actor":"director") by m into sg
                orderby sg.Key.year
                select new { title = sg.Key.Title, roles = sg } )
     };

Я также взял некоторые подсказки из Aaronaught и попытался начать с таблицы Credits и использовать сгенерированные ассоциации. Это сделало вещи проще. Этот код также работает:

var persons =
    from c in db.Credits
    where c.PersonId == arg
    group c by c.People into g
    select new
    {
        name = g.Key.Name,
        credits = from cr in g
            group ((cr.Role == "A") ? "actor" : "director")
            by cr.Movies into g2
            orderby g2.Key.Year
            select new { title = g2.Key.Title, roles = g2 }
    };

... и выдает тот же (или эквивалентный) вывод при сериализации JavaScriptSerializer.


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

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