c# - left - Cómo limitar un LINQ combinación externa izquierda a una fila
linq join c# ejemplos (3)
Tengo una combinación externa izquierda (abajo) que arroja los resultados como se esperaba. Necesito limitar los resultados de la tabla "derecha" al "primer" golpe. ¿Puedo hacer eso de alguna manera? Actualmente, obtengo un resultado para cada registro en ambas tablas, solo quiero ver un resultado de la tabla de la izquierda (ítems) sin importar cuántos resultados tenga en la tabla correcta (fotos).
var query = from i in db.items
join p in db.photos
on i.id equals p.item_id into tempPhoto
from tp in tempPhoto.DefaultIfEmpty()
orderby i.date descending
select new
{
itemName = i.name,
itemID = i.id,
id = i.id,
photoID = tp.PhotoID.ToString()
};
GridView1.DataSource = query;
GridView1.DataBind();
Esto hará el trabajo por ti.
from i in db.items
let p = db.photos.Where(p2 => i.id == p2.item_id).FirstOrDefault()
orderby i.date descending
select new
{
itemName = i.name,
itemID = i.id,
id = i.id,
photoID = p == null ? null : p.PhotoID.ToString();
}
Obtuve este sql cuando lo generé contra mi propio modelo (y sin el nombre y las segundas columnas de id en la proyección).
SELECT [t0].[Id] AS [Id], CONVERT(NVarChar,(
SELECT [t2].[PhotoId]
FROM (
SELECT TOP (1) [t1].[PhotoId]
FROM [dbo].[Photos] AS [t1]
WHERE [t1].[Item_Id] = ([t0].[Id])
) AS [t2]
)) AS [PhotoId]
FROM [dbo].[Items] AS [t0]
ORDER BY [t0].[Id] DESC
Cuando pregunté por el plan, mostró que la subconsulta se implementa mediante esta unión:
<RelOp LogicalOp="Left Outer Join" PhysicalOp="Nested Loops">
Lo que quieres hacer es agrupar la mesa. La mejor manera de hacer esto es:
var query = from i in db.items
join p in (from p in db.photos
group p by p.item_id into gp
where gp.Count() > 0
select new { item_id = g.Key, Photo = g.First() })
on i.id equals p.item_id into tempPhoto
from tp in tempPhoto.DefaultIfEmpty()
orderby i.date descending
select new
{
itemName = i.name,
itemID = i.id,
id = i.id,
photoID = tp.Photo.PhotoID.ToString()
};
Editar: Este es David B hablando. Solo estoy haciendo esto porque Nick me lo pidió. Nick, modifique o elimine esta sección como lo considere apropiado.
El SQL generado es bastante grande. El int 0 (para ser comparado con el recuento) se pasa a través del parámetro.
SELECT [t0].X AS [id], CONVERT(NVarChar(MAX),(
SELECT [t6].Y
FROM (
SELECT TOP (1) [t5].Y
FROM [dbo].[Photos] AS [t5]
WHERE (([t4].Y IS NULL) AND ([t5].Y IS NULL)) OR (([t4].Y IS NOT NULL) AND ([t5].Y IS NOT NULL) AND ([t4].Y = [t5].Y))
) AS [t6]
)) AS [PhotoId]
FROM [dbo].[Items] AS [t0]
CROSS APPLY ((
SELECT NULL AS [EMPTY]
) AS [t1]
OUTER APPLY (
SELECT [t3].Y
FROM (
SELECT COUNT(*) AS [value], [t2].Y
FROM [dbo].[Photos] AS [t2]
GROUP BY [t2].Y
) AS [t3]
WHERE (([t0].X) = [t3].Y) AND ([t3].[value] > @p0)
) AS [t4])
ORDER BY [t0].Z DESC
El plan de ejecución revela tres combinaciones a la izquierda. Al menos uno es trivial y no debe contarse (trae el cero). Hay suficiente complejidad aquí que no puedo señalar claramente ningún problema de eficiencia. Podría funcionar genial.
Podrías hacer algo como:
var q = from c in
(from s in args
select s).First()
select c;
Alrededor de la última parte de la consulta. No estoy seguro si funcionará o qué tipo de SQL wack producirá :)