Cross Apply - Linq agli oggetti
-
13-12-2019 - |
Domanda
In T-SQL è possibile utilizzare CROSS APPLY
per ottenere tutte le possibili variazioni tra la tabella a sinistra e destra dalla dichiarazione. Ora ho la seguente situazione in C#
e spero che ci sia un modo per risolvere il mio problema usando linq-to-oggetti.
Ho una lista con oggetti TestData
(come di seguito) che è simile all'oggetto KeyValuePair<string, object>
(solo un Key
e una proprietà Value
):
La chiave può essere tutto e ci possono essere più oggetti con lo stesso tasto.
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
.
Ho anche un elenco di tasti richiesti:
IList<String> requestedKeys = new List<string>() { "A", "D" };
.
Ora voglio avere tutte le possibili combinazioni di oggetti KeyValuepair tra i tasti nell'elenco requestedKeys
.
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 |
.
È possibile risolvere il mio problema usando linq-to-oggetti. Se no, puoi dirmi il modo più efficiente per costruirlo comunque.
.
Modifica 1:
Per rendere più chiaro ciò che il risultato dovrebbe essere:
Voglio avere una query linq-to-oggetti qualcosa del genere:
@Joanna Grazie per il suggerimento su più generatori from
s, ma il problema è: con questa sintassi non è possibile avere una quantità dinamica di from
s. Nel mio caso ho bisogno di tanti from
s come elementi nell'elenco 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 */ }
. Soluzione 2
Ho trovato la soluzione stessa:
È un join molto complesso in LINQ perché ogni elemento nell'elenco ReadyKeys richiede un cross-join extra.Per quanto riguarda l'elenco specificato di esempio, il risultato dovrebbe essere objects.Count(m => m.Key == "A") * objects.Count(m => m.Key == "D")
(il risultato è 3 * 2= 6).Ogni elemento extra nell'elenco provoca un moltiplicatore extra di tutto il set di risultati.
Quindi questo è il risultato:
// 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;
.
Sfortunatamente non è completamente linq quindi se qualcuno conosce una soluzione migliore.Si prega di commentarmi o rispondi alla mia domanda :)
Altri suggerimenti
Qualcosa lungo queste linee dovrebbe funzionare:
var filtered = objects
.Where(o => requestedKeys.Contains(o.Key));
var crossJoined = from el1 in filtered
from el2 in filtered
select new [] {el1, el2};
.
Il croce Iscriviti si ottiene concatenando più clausole from
.
Modifica:
In questo caso non riesco a pensare a un modo più semplice per farlo rispetto a quello che hai iniziato nella tua modifica.L'unica cosa mancante è selezionare i valori:
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 */ }
. Utente in questo modo può presentare domanda Genreare 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();
.
Infine, lo SQL è:
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
. objects
.Join(requestedKeys, o => o.Key, rk => rk, (o, rk) => o)
.SelectMany(o => requestedKeys.Select(k => new {Key = k, Value = o.Value}));
.