Come filtrare le raccolte figlio in Linq
-
22-07-2019 - |
Domanda
Devo filtrare gli elementi figlio di un'entità in linq usando una singola query linq. È possibile?
Supponiamo di avere due tabelle correlate. Versi e VerseTraduzioni. L'entità creata da LINQ to SQL è tale che ho un oggetto Verse che contiene un oggetto figlio che è una raccolta di VerseTranslation.
Ora, se ho la seguente query linq
var res = from v in dc.Verses
where v.id = 1
select v;
Ottengo una raccolta di versetti il ??cui ID è 1 e ogni oggetto verso contiene tutti gli oggetti figlio di VerseTranslations.
Quello che voglio anche fare è filtrare quell'elenco figlio di Verse Translations.
Finora l'unico modo in cui sono stato in grado di inventarmi è usare un nuovo tipo Anonimo o altro. Come segue
var res= from v in dc.Verses
select new myType
{
VerseId = v.VerseId,
VText = v.Text,
VerseTranslations = (from trans in v.VerseTranslations
where languageId==trans.LanguageId
select trans
};
Il codice sopra funziona, ma ho dovuto dichiarare una nuova classe per questo. Non c'è modo di farlo in modo tale che il filtro sulla tabella figlio possa essere incorporato nella prima query linq in modo che nessuna nuova classe debba essere dichiarata.
Saluti, MAC
Soluzione
Quindi finalmente sono riuscito a farlo funzionare grazie ai suggerimenti forniti da Shiraz.
DataLoadOptions options = new DataLoadOptions();
options.AssociateWith<Verse>(item => item.VerseTranslation.Where(t => languageId.Contains(t.LanguageId)));
dc.LoadOptions = options;
var res = from s in dc.Verse
select s;
Ciò non richiede la proiezione o l'utilizzo di nuove classi di estensione.
Grazie per tutte le persone che hai inserito.
Altri suggerimenti
Filtro sulla raccolta racchiusa dell'oggetto,
var res = dc.Verses
.Update(v => v.VerseTranslations
= v.VerseTranslations
.Where(n => n.LanguageId == languageId));
Utilizzando il metodo di estensione " Aggiorna " da HookedOnLinq
public static class UpdateExtensions {
public delegate void Func<TArg0>(TArg0 element);
/// <summary>
/// Executes an Update statement block on all elements in an IEnumerable<T> sequence.
/// </summary>
/// <typeparam name="TSource">The source element type.</typeparam>
/// <param name="source">The source sequence.</param>
/// <param name="update">The update statement to execute for each element.</param>
/// <returns>The numer of records affected.</returns>
public static int Update<TSource>(this IEnumerable<TSource> source, Func<TSource> update) {
if (source == null) throw new ArgumentNullException("source");
if (update == null) throw new ArgumentNullException("update");
if (typeof(TSource).IsValueType)
throw new NotSupportedException("value type elements are not supported by update.");
int count = 0;
foreach(TSource element in source) {
update(element);
count++;
}
return count;
}
}
Se proviene da un database, è possibile eseguire la prima istruzione.
Quindi esegui un caricamento o inclusione di VerseTranslations con una clausola Where.
http://msdn.microsoft.com/en-us/library /bb896249.aspx
Hai una relazione nel tuo modello tra Verse e VerseTranslations. In tal caso potrebbe funzionare:
var res= from v in
dc.Verses.Include("VerseTranslations").Where(o => languageId==o.LanguageId)
select v;
Non c'è modo di farlo in un tale modo tale che il filtro sul tabella figlio può essere incorporata in prima query linq in modo che nessuna novità le classi devono essere dichiarate?
Tecnicamente, la risposta è no. Se stai cercando di restituire più dati di quanti un oggetto di una singola entità (Verse, VerseTranslation) può contenere, avrai bisogno di una sorta di oggetto per " progetto " in. Tuttavia, puoi aggirare dichiarando esplicitamente myType
utilizzando un tipo anonimo:
var res = from v in dc.Verses
select new
{
Verse = v,
Translations = (from trans in v.VerseTranslations
where languageId==trans.LanguageId
select trans).ToList()
};
var first = res.First();
Console.WriteLine("Verse {0} has {1} translation(s) in language {2}.",
first.Verse.VerseId, first.Translations.Count, languageId);
Il compilatore genererà una classe con proprietà Verse e Translations digitate in modo appropriato. È possibile utilizzare questi oggetti praticamente per tutto il tempo in cui non è necessario fare riferimento al tipo in base al nome (ad esempio per tornare da un metodo denominato). Quindi, mentre non stai tecnicamente "dichiarando" un tipo, stai ancora utilizzando un nuovo tipo che verrà generato in base alle tue specifiche.
Per quanto riguarda l'utilizzo di una singola query LINQ, tutto dipende da come si desidera strutturare i dati. Per me sembra che la tua query originale abbia più senso: accoppia ogni Verse
con un elenco filtrato di traduzioni. Se ti aspetti una sola traduzione per lingua, puoi utilizzare SingleOrDefault
(o FirstOrDefault
) per appiattire la tua subquery o semplicemente usare un SelectMany
come in questo modo:
var res= from v in dc.Verses
from t in v.VerseTranslations.DefaultIfEmpty()
where t == null || languageId == t.LanguageId
select new { Verse = v, Translation = t };
Se un verso ha più traduzioni, questo restituirà una "riga" per ogni coppia Verse / Translation. Uso DefaultIfEmpty ()
come join sinistro per essere sicuro che avremo tutti i versetti anche se manca una traduzione.