[英]Linq-to-SQL: How to shape the data with group by?
I have an example database, it contains tables for Movies, People and Credits. 我有一个示例数据库,它包含电影,人物和积分的表格。 The Movie table contains a Title and an Id. 电影表包含标题和标识。 The People table contains a Name and an Id. People表包含Name和Id。 The Credits table relates Movies to the People that worked on those Movies, in a particular role. Credits表将电影与处理这些电影的人物联系起来,具有特定的作用。 The table looks like this: 该表如下所示:
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
In this simple example, the [Role] column is a single character, by my convention either 'A' to indicate the person was an actor on that particular movie, or 'D' for director . 在这个简单的例子中,[Role]列是一个单一的字符,按照我的惯例,'A'表示该人是该特定电影的演员 ,或'D'表示导演 。
I'd like to perform a query on a particular person that returns the person's name, plus a list of all the movies the person has worked on, and the roles in those movies. 我想对返回此人姓名的特定人员,以及该人员所处理的所有电影的列表以及这些电影中的角色执行查询。
If I were to serialize it to json, it might look like this: 如果我将它序列化为json,它可能看起来像这样:
{
"name" : "Clint Eastwood",
"movies" : [
{ "title": "Unforgiven", "roles": ["actor", "director"] },
{ "title": "Sands of Iwo Jima", "roles": ["director"] },
{ "title": "Dirty Harry", "roles": ["actor"] },
...
]
}
How can I write a LINQ-to-SQL query that shapes the output like that? 如何编写一个LINQ-to-SQL查询来对输出进行整形?
I'm having trouble doing it efficiently. 我无法有效地做到这一点。
if I use this query: 如果我使用此查询:
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")
})
};
I get something like this: 我得到这样的东西:
{
"name" : "Clint Eastwood",
"movies" : [
{ "title": "Unforgiven", "role": "actor" },
{ "title": "Unforgiven", "role": "director" },
{ "title": "Sands of Iwo Jima", "role": "director" },
{ "title": "Dirty Harry", "role": "actor" },
...
]
}
That's not quite right. 那不太对劲。 As you can see there's a duplicate of each movie for which Eastwood played multiple roles. 正如你所看到的,伊斯特伍德扮演多个角色的每部电影都有重复。 I would expect that because there are multiple rows in the credits table for that movie+person combination, one for each role. 我希望这是因为该电影+人物组合的信用表中有多行,每个角色一行。
I thought I'd use a group by , like this: 我以为我会使用一个小组 ,像这样:
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 })
};
The output is pretty close to what I want. 输出非常接近我想要的。 It looks like this: 它看起来像这样:
{
"name" : "Clint Eastwood",
"movies" : [
{ "roles": ["actor", "director"]},
{ "roles": ["director"]},
{ "roles": ["actor"]},
...
]
}
That's close, but of course I don't have the movie titles. 那很接近,但我当然没有电影片头。
If I use a group by and include the movie title, like this: 如果我使用分组并包含电影标题,如下所示:
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 })
};
...then it won't compile, due to ...然后它将无法编译,因为
error CS0103: The name 'm' does not exist in the current context 错误CS0103:当前上下文中不存在名称“m”
How can I shape the output the way I want? 如何按照我想要的方式塑造输出?
It's a lot easier to reason about if you start from the relation table (credits): 如果从关系表(信用证)开始,可以更容易推理:
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")
}
};
Here's code that will display the results: 这是显示结果的代码:
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);
}
}
I believe that you'll need to materialize the query, then group by name and title and use string.Join to collate the roles. 我相信您需要实现查询,然后按名称和标题分组并使用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
});
Thanks to the hint from tvanfosson , I was able to come up with this, which works for me! 感谢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 } )
};
I also took some hints from Aaronaught and tried starting with the Credits table, and using the generated associations. 我还从Aaronaught那里得到了一些提示,并尝试从Credits表开始,并使用生成的关联。 That made things simpler. 这使事情变得更简单。 This code also works: 此代码也有效:
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 }
};
...and it produces the same (or equivalent) output when serialized the the JavaScriptSerializer. ...当序列化JavaScriptSerializer时,它会产生相同(或等效)的输出。
The key realization for me, that allowed me to get this done, was that I could use a compound key for the group, and that I could select on the fields within the key. 让我完成这项工作的关键实现是,我可以为组使用复合键,并且我可以选择键中的字段。 The second key realization was that I should use the generated associations. 第二个关键的实现是我应该使用生成的关联。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.