Aplicación cruzada: LINQ a objetos

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 froms pero el problema es: con esta sintaxis no puede tener una cantidad dinámica de froms. En mi caso necesito tantos froms 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 */ }

preguntado el 22 de mayo de 12 a las 17:05

Entonces, ¿qué tiene de malo la respuesta de @Joanna? (¿y tu edición?) -

@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. -

4 Respuestas

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 froms 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

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

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

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 or haz tu propia pregunta.