Aplicación cruzada: LINQ a objetos
Frecuentes
Visto 5,364 veces
2
En T-SQL puedes usar CROSS APPLY
para obtener todas las posibles variaciones entre la tabla izquierda y derecha de la declaración. Ahora tengo la siguiente situación en C#
y espero que haya una manera de resolver mi problema usando LINQ-to-Objects.
Tengo una lista con TestData
objetos (como a continuación) que es similar a la KeyValuePair<string, object>
objeto (Solo un Key
y Value
property): La clave puede ser todo y puede haber múltiples objetos con la misma clave.
IList<KeyValuePair<String, Object>> objects;
// Content of list
// # | Key | Value
// 1 | "A" | 1
// 2 | "A" | 2
// 3 | "A" | 3
// 4 | "B" | 4
// 5 | "B" | 5
// 6 | "C" | 6
// 7 | "D" | 7
// 8 | "D" | 8
También tengo una lista de claves solicitadas:
IList<String> requestedKeys = new List<string>() { "A", "D" };
Ahora quiero tener todas las combinaciones posibles de objetos KeyValuePair entre las claves en el requestedKeys
lista.
IList<IList<KeyValuePair<String, Object>>> result = ...
// Content of 'result' will be in this example 6 lists with each 2 KeyValuePair objects
// # | "A" | "D" | (If there are more in the requestedKey list then there are more KeyValuePair items in the innerlist.)
// 1 | 1 | 7 |
// 2 | 2 | 7 |
// 3 | 3 | 7 |
// 4 | 1 | 8 |
// 5 | 2 | 8 |
// 6 | 3 | 8 |
¿Es posible resolver mi problema usando LINQ-to-Objects? Si no, ¿puede decirme la forma más eficiente de construirlo de todos modos?
EDIT 1:
Para dejar más claro cuál debería ser el resultado:
Quiero tener una consulta LINQ-to-Objects algo como esto:
@Joanna gracias por el consejo sobre múltiples from
s pero el problema es: con esta sintaxis no puede tener una cantidad dinámica de from
s. En mi caso necesito tantos from
s como artículos en el requestedKeys
--
var result =
from listA in objects.Where(m => m.Key == "A")
from listD in objects.Where(m => m.Key == "D")
// from .....
// from .....
// overhere as many froms as items in 'requestedKeys' list
select new [] { listA, listD /*, All other lists */ }
4 Respuestas
3
Algo en estas líneas debería funcionar:
var filtered = objects
.Where(o => requestedKeys.Contains(o.Key));
var crossJoined = from el1 in filtered
from el2 in filtered
select new [] {el1, el2};
La unión cruzada se logra encadenando múltiples from
cláusulas.
EDIT:
En este caso, no puedo pensar en una forma más fácil de hacerlo que la que comenzaste en tu edición. Lo único que falta es seleccionar los valores:
var result =
from listA in objects.Where(m => m.Key == "A").Select(m => m.Value)
from listD in objects.Where(m => m.Key == "D").Select(m => m.Value)
// from .....
// from .....
// overhere as many froms as items in 'requestedKeys' list
select new [] { listA, listD /*, All other lists */ }
contestado el 23 de mayo de 12 a las 13:05
Gracias por tu respuesta. Me alegra saber que puedo usar varias cláusulas from pero ahora realiza una combinación cruzada entre todos los valores en filtered
. Pero quiero tener una lista separada para cada elemento en requestedKeys
para cruzar aplicar. Mira mi última edición. - hwcverwe
@hwcverwe: edité el código que publicaste para seleccionar valores; ¿no estás seguro de si eso ya es lo que querías? - joanna derks
el problema es que tienes una cantidad dinámica de from
s y una lista interna de resultados con una cantidad dinámica de elementos. (cantidad de from
y la cantidad de elementos en la lista interna es igual a la cantidad de claves solicitadas). He resuelto el problema hoy. Mira mi respuesta. Es mucho más complejo de lo que pensaba al principio. Gracias por tu ayuda - hwcverwe
1
Yo mismo encontré la solución:
Es una combinación muy compleja en LINQ porque cada elemento de la lista requestKeys requiere una combinación cruzada adicional. Con respecto a la lista de ejemplo dada, el resultado debe ser objects.Count(m => m.Key == "A") * objects.Count(m => m.Key == "D")
(el resultado es 3 * 2 = 6). Cada elemento adicional en la lista genera una multiplicación adicional de todo el conjunto de resultados.
Así que este es el resultado:
// The result list
IEnumerable<IList<KeyValuePair<char, int>>> result;
// If there are no requestedKeys there is no result expected
if(requestedKeys.Count() > 0)
{
// Loop through all request keys to cross join them together
foreach (var key in requestedKeys)
{
if (result == null)
{
// First time the innerlist List<KeyValuePair<char, int>> will contain 1 item
// Don't forget to use ToList() otherwise the expression will be executed to late.
result = objects.Where(m => m.Key == key).Select(m => new List<KeyValuePair<char, int>>() { m }).ToList();
}
else
{
// Except for the first time the next subresult will be cross joined
var subresult = objects.Where(m => m.Key == key).Select(m => new List<KeyValuePair<char, int>>() { m });
result = result.Join(
subresult,
l1 => 0, // This and the next parameter does the cross join trick
l2 => 0, // This and the previous parameter does the cross join trick
(l1, l2) => l1.Concat(l2).ToList() // Concat both lists which causes previous list plus one new added item
).ToList(); // Again don't forget to 'materialize' (I don't know it is called materialization in LINQ-to-Objects
// but it has simular behaviors because the expression needs to be executed right away)
}
}
}
return result;
Desafortunadamente, no es completamente LINQ, así que si alguien conoce una mejor solución. Por favor comentenme o respondan mi pregunta :)
contestado el 23 de mayo de 12 a las 13:05
¡Quiero rascarme los ojos cuando veo soluciones extrañas como esta...! La consulta es muy sencilla: RequestKeys.GroupJoin(objects, key => key, m => m.Key, (key, ms) => new {Key, ms}).where(keygrp => keygrp.Any()) - Morten Gorm Madsen
@MortenGormMadsen. Su respuesta no da el resultado solicitado. Esta respuesta se dio en 2012, con suerte ahora hay mejores formas. Todavía espero que pueda encontrar una mejor solución porque estoy de acuerdo en que este código no es realmente legible: hwcverwe
1
el usuario de esta manera puede generar una aplicación cruzada de sql:
var comments = AppCommentRepository.Where(com => com.iAction > -1 && productIds.Contains(com.sProductId))
.GroupBy(c => c.sProductId)
.SelectMany(p => p.OrderByDescending(cc => cc.dAddTime).Take(commentNum)).ToList();
finalmente, el sql es:
SELECT [t3].[iCommentId], .....FROM (
SELECT [t0].[sProductId]
FROM [dbo].[App_Comment] AS [t0]
WHERE ([t0].[iAction] > -1) --AND ([t0].[sProductId] IN (@p1))
GROUP BY [t0].[sProductId]
) AS [t1]
CROSS APPLY (
SELECT TOP (2) [t2].[iCommentId],......
FROM [dbo].[App_Comment] AS [t2]
WHERE ([t1].[sProductId] = [t2].[sProductId]) AND ([t2].[iAction] > -1)
-- AND ([t2].sProductId] IN (@p1))
ORDER BY [t2].[dAddTime] DESC
) AS [t3]
ORDER BY [t3].sProductId DESC
Respondido el 15 de enero de 14 a las 10:01
0
objects
.Join(requestedKeys, o => o.Key, rk => rk, (o, rk) => o)
.SelectMany(o => requestedKeys.Select(k => new {Key = k, Value = o.Value}));
respondido 12 nov., 16:16
No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas c# linq linq-to-objects cross-apply or haz tu propia pregunta.
blogs.msdn.com/b/ericlippert/archive/2010/06/28/… - Dave Bish
Entonces, ¿qué tiene de malo la respuesta de @Joanna? (¿y tu edición?) - leppie
@leppie no es lo suficientemente dinámico. por ejemplo, ¿qué sucede si solicito todas las combinaciones posibles con AB y C? Entonces necesita tener una tercera
from
y un tercer KeyValuePair en la lista de resultados. Así que quiero saber cómo hacerlo dinámico. - hwcverwe