c# - inner - linq to entities join
¿Cómo usar LINQ para seleccionar un objeto? (3)
Digamos que tienes lo siguiente:
public class SongsForUser
{
public int UserId;
public List<int> Songs;
}
Entonces una función como esta aquí servirá. La lista es solo para tener algunos datos para probar.
public void Group()
{
List<Tuple<int, int>> SongRelations = new List<Tuple<int, int>>();
SongRelations.Add(new Tuple<int, int>(1, 1));
SongRelations.Add(new Tuple<int, int>(1, 4));
SongRelations.Add(new Tuple<int, int>(1, 12));
SongRelations.Add(new Tuple<int, int>(2, 95));
var list = SongRelations.GroupBy(s => s.Item1)
.Select(r => new SongsForUser()
{
UserId = r.Key,
Songs = r.Select(t => t.Item2).ToList(),
});
}
list
contiene 2 elementos de tipo SongsForUser después. Uno con el usuario 1 y una lista de canciones que contienen 1, 4 y 12 y otro con el usuario 2 y una lista de canciones que contienen 95.
Tengo datos que se ven así:
UserId | SongId
-------- --------
1 1
1 4
1 12
2 95
También tengo la siguiente clase:
class SongsForUser
{
public int User;
public List<int> Songs;
}
Lo que me gustaría hacer es usar LINQ para seleccionar de mis datos para crear una colección de objetos SongsForUser. A continuación es lo que he encontrado hasta ahora:
var userCombos = songs.UserSongs.Select(x => new SongsForUser() { User = x.UserId,
Songs = /*What goes here?*/ });
¿Cómo haría para poblar mi lista de Songs
?
Así que el resultado debe ser dos objetos SongsForUser. Para el usuario 1
tendría 3 elementos en la lista de Songs
. Para el usuario 2
tendría 1 elemento en la lista de Songs
.
Sospecho que quieres:
var songsByUser = songs.UserSongs
.GroupBy(song => song.UserId, song => song.SongId)
.Select(g => new SongsForUser { User = g.Key,
Songs = g.ToList() });
Para explicar, después de GroupBy
tendrás un grupo de grupos, donde la clave de cada grupo es la ID de usuario y los valores dentro del grupo son las ID de canciones:
Key = 1, Values = 1, 4, 12
Key = 2, Value = 95
Entonces solo estás convirtiendo eso en tu tipo SongsForUser
. Tenga en cuenta que no necesita incluir explícitamente el ()
al llamar al constructor en un inicializador de objetos, es implícito a menos que necesite especificar los argumentos del constructor.
Podrías hacer todo esto en una llamada GroupBy
, por cierto:
var songsByUser = songs.UserSongs
.GroupBy(song => song.UserId, song => song.SongId,
(user, ids) => new SongsForUser { User = user,
Songs = ids.ToList() });
Personalmente, generalmente encuentro que una llamada Select
separada es más legible.
También puedes hacer todo esto con una expresión de consulta:
var songsByUser = from song in songs.UserSongs
group song.SongId by song.UserId into g
select new SongsForUser { User = g.Key, Songs = g.ToList() };
EDITAR: Lo anterior es "proveedor neutral" pero parece que no está funcionando con LINQ para Entidades. Es posible que puedas hacer que funcione así:
var songsByUser = songs.UserSongs
.GroupBy(song => song.UserId, song => song.SongId)
.AsEnumerable()
.Select(g => new SongsForUser { User = g.Key,
Songs = g.ToList() });
La llamada AsEnumerable
obligará a que la agrupación se realice en la base de datos, pero la proyección final (incluida la llamada ToList
) se realizará localmente. Sin embargo, debe comprobar la eficacia del SQL generado.
songs.UserSongs.GroupBy(x => x.User).Select(g => new SongsForUser()
{
User = g.Key,
Songs = g.Select(s => s.SongId).ToList()
});