2011-12-28 16 views
5

Ecco una situazione in cui mi imbatto frequentemente: ho oggetti Parent e Child e l'oggetto Child ha una proprietà Parent. Voglio eseguire una query che ottiene gli oggetti figlio e si unisce a ciascuno di l'oggetto padre corretto:Qual è il modo giusto per selezionare e combinare oggetti con LINQ?

Dim db = New DataContextEx() 

get the children, along with the corresponding parent 
Dim Children = From x In db.ChildTable 
       Join y In db.ParentTable 
       On x.ParentId Equals y.Id 
       Execute x.Parent = y  <-- pseudocode 
       Select x 

La pseudo mostra quello che voglio realizzare: per restituire l'oggetto figlio x, ma con il codice dopo la (falso) Esegui dichiarazione eseguita. Posso pensare a molti modi per raggiungere l'obiettivo finale, ma tutti hanno molte più linee di codice e/o creazione di oggetti temporanei o funzioni che trovo poco eleganti. (Nota questa è la sintassi VB.NET, ma non è una domanda di sintassi VB, poiché AFAIK C# avrebbe lo stesso problema.)

Quindi quale sarebbe il modo più pulito per fare ciò che sto cercando di fare?

Modifica: le persone hanno chiesto quale ORM sto usando, ma questa è una domanda LINQ semplice e lineare; Non sto cercando di convertire questo in logica per essere eseguito sul server, voglio solo un po 'di zucchero sintattico per eseguire il lato client del codice dopo che la query è stata eseguita sul server.

+0

Che cos'è x.Parent nel codice se x e y sono record tabella? – Paul

+0

Può essere d'aiuto se si indica quale fornitore si sta utilizzando. Linq-to-SQL, Linq-to-Entities, ecc., E inoltre se si può capitare di utilizzare prima il codice EF. –

+0

Sono solo oggetti fortemente tipizzati dal mio ORM, ma in realtà ciò si applicherebbe ugualmente a Linq To Objects. –

risposta

2

Questi suggerimenti, in particolare @Paul's, sono stati molto utili, ma la mia versione finale è abbastanza diversa che ho intenzione di scrivere come la mia risposta.

I implementata la seguente funzione di estensione del tutto generale:

<Extension()> _ 
Public Function Apply(Of T)(ByVal Enumerable As IEnumerable(Of T), ByVal action As Action(Of T)) As IEnumerable(Of T) 
    For Each item In Enumerable 
     action(item) 
    Next 

    Return Enumerable 
End Function 

Questo è lo stesso come ForEach, eccetto che restituisce la sequenza in ingresso, e che il nome Applicare rende chiaro che ci sono lato effetti. Allora posso scrivere la mia domanda come:

Dim Children = (
      From x In db.ChildTable 
      Join y In db.ParentTable 
      On x.ParentId Equals y.Id 
      ). 
      Apply(Sub(item) item.x.Parent = item.y). 
      Select(Function(item) item.x) 

Sarebbe naturalmente meglio se ci fosse un modo per avere operatori di query personalizzati, quindi non avrebbe dovuto usare la sintassi lambda, ma anche così questo sembra molto pulito e riutilizzabile. Grazie ancora per tutto l'aiuto.

+1

Per migliorare questa risposta, suggerirei di modificare "Applica" per operare su un singolo oggetto. Quindi, la sintassi sarebbe più pulita: 'Seleziona x.Apply (Sub (c) c.Parent = y)' (È una versione più generica della risposta di @ Paul) –

+0

@ScottRippey: +1 per il buon suggerimento. Sembra più pulito e toglie gli effetti collaterali. Proverò in entrambi i modi e vedrò quale si sente più naturale in uso. –

+0

@ScottRippey: l'ho fatto e ho deciso di mantenere entrambi i metodi Apply, poiché entrambi sembrano applicabili in varie situazioni. Grazie ancora. –

11

È possibile utilizzare un tipo anonimo durante la proiezione dei risultati. Esempio C#:

var items = from x In db.ChildTable 
      join y In db.ParentTable on x.ParentId equals y.Id 
      select new { Child =x , Parent=y }; 

Quindi assegnare le proprietà padre.

foreach(var item in items) 
{ 
    item.Child.Parent = item.Parent; 
} 

return items.Select(item => item.Child); 

Inoltre, si consiglia di utilizzare alcune soluzioni ORM per questo anziché il proprio.

+0

Questo produce un oggetto IEnumerable (Of ), non IEnumerable (Of Child), che è quello che sto cercando di fare. –

+1

Ok ho aggiornato il codice. È meglio usare foreach loop quando si manipolano i dati –

+4

@JoshuaFrank - Sembra che si stia tentando di introdurre effetti collaterali (impostando una proprietà) in una query LINQ. LINQ (e la programmazione funzionale in generale) non è progettata per farlo. Creare una nuova istanza (Figlio o tipo anonimo) in Seleziona o aggiungere un foreach successivo. – TrueWill

2

Si può fare in questo modo:

aggiungere un metodo per fare la tua logica per classe di bambini che restituisce this o Me in VB

class Child 
{ 
    public Child GetWithAssignedParent(Parent y) 
    { 
     this.Parent = y; 
     return this; 
    } 
} 

uso questo metodo nella query di selezione a fare la logica e restituire le istanze aggiornate di classe del bambino:

var children = from x In db.ChildTable 
join y In db.ParentTable on x.ParentId equals y.Id 
select x.GetWithAssignedParent(y); 

o utilizzare Func <>

var children = from x in children 
join y in parents on x.ParentId equals y.Id 
select 
    new Func<Child>(() => 
    { 
     x.Parent = y; 
     return x; 
    }).Invoke(); 
+0

+1 per il buon suggerimento, e lo stavo facendo da un po ', ma non mi sta abbastanza bene con me perché (1) richiede la modifica della classe per aggiungere una funzione di utilità per una situazione che può essere utile solo in un posto; e (2) nel luogo in cui è chiamato, non è così ovvio ciò che quel metodo fa senza trapanarlo. –

+0

potresti creare un metodo di estensione per non aggiungere codice aggiuntivo alla classe originale. Non ho familiarità con il tuo codice, ma probabilmente potresti trovare un nome di metodo corretto che sarà chiaro nel tuo caso particolare. – Paul

+0

oppure potresti creare un metodo di utilità (metodo statico) che considererà padre e figlio come due parametri, assegnerà il genitore alla proprietà del figlio e restituirà il figlio aggiornato. – Paul

0

Ecco probabilmente la soluzione più semplice:

var children = from x in db.ChildTable 
       join y in db.ParentTable 
       on x.ParentId equals y.Id 
       // Assign the Parent to the Child: 
       let parent = (x.Parent = y) // <-- Inline assignment (with a dummy variable "parent") 
       select x; 

responsabilità: questa ricerca ha assegnazione in linea e modifica dello stato ... entrambi i quali non sono considerati "best practice".
Tuttavia, è molto facile da leggere e comprendere, manutenibile e produce facilmente i risultati corretti, quindi direi che è un'ottima soluzione.

Inoltre, questo funzionerà solo in C#, perché VB.NET non dispone di assegnazione in linea.

+0

Destra, solo C#, ma IMO è anche il tipo di codice che non dovrebbe essere scritto in C#, perché dipende in modo flagrante da qualcosa di difficile; Non vorrei guardarlo tra un mese e cercare di capire cosa sta facendo. –

+0

Concordato, ma in questo scenario, un semplice commento '// Assegna il genitore al bambino:' è più che sufficiente per auto-documentare il codice. Le altre soluzioni ('foreach' loop, metodo di estensione) impiegheranno sicuramente più tempo per capire e richiedono molto più codice da leggere. –

0

Se si utilizza LinqToObjects, utilizzare questo codice per assegnare i genitori ai bambini.

ILookup<int, Child> lookup = children.ToLookup(c => c.ParentId) 

foreach(Parent p in parents) 
{ 
    foreach(Child c in lookup[p.Id]) 
    { 
    c.Parent = p; 
    } 
} 

Se stai usando LinqToSql, uso DataLoadOptions per caricare i record correlati.


Se si utilizza LinqToEntities, utilizzare .Include per caricare i record correlati.

+0

Come ho detto nella mia modifica, sto cercando di risolvere questo problema in Linq in generale, non solo per gli oggetti estratti da un db. –

+0

@Joshua Frank, Come ho detto nel primo 50% della mia risposta, c'è un modo semplice per modificare oggetti che non sono in un database. –

+0

Semplice sì, ma altamente dettagliato, che è la maggior parte di ciò che sto cercando di evitare. –

Problemi correlati