2009-06-29 30 views
5

Sto provando a convertire alcune query SQL in Linq per evitare più viaggi nel database.Linq: calcola più medie in una query

Il vecchio SQL che sto cercando di convertire lo fa:

SELECT 
    AVG(CAST(DATEDIFF(ms, A.CreatedDate, B.CompletedDate) AS decimal(15,4))), 
    AVG(CAST(DATEDIFF(ms, B.CreatedDate, B.CompletedDate) AS decimal(15,4))) 
FROM 
    dbo.A 
INNER JOIN 
    dbo.B ON B.ParentId = A.Id 

Così ho creato due classi C#:

class B 
{ 
    public Guid Id { get; set; } 
    public DateTime CreatedDate { get; set; } 
    public DateTime CompletedDate { get; set; } 
} 

class A 
{ 
    public Guid Id { get; set; } 
    public DateTime CreatedDate { get; set; } 
    public List<B> BList { get; set; } 
} 

E ho un oggetto List<A> che voglio query. È popolato dal database, quindi ogni A nell'elenco ha una sotto-lista con un carico di Bs. Voglio usare Linq-to-objects per interrogare questo elenco.

Quindi ho bisogno di usare Linq per ottenere il tempo medio tra l'inizio di una A e il completamento dei suoi figli B, e il tempo medio tra l'inizio e il completamento di ogni B. Non ho scritto l'SQL originale, quindi non sono del tutto sicuro che faccia quello che dovrebbe!

Ho alcune di queste medie da calcolare, quindi mi piacerebbe farle tutte all'interno di una query Linq magica. È possibile?

risposta

5

La domanda più interessante sarebbe come fare una cosa del genere sul server, ad esempio per fare la seguente query si traducono in LINQ to SQL.

 var q = from single in Enumerable.Range(1, 1) 
       let xs = sourceSequence 
       select new 
       { 
        Aggregate1 = xs.Sum(), 
        Aggregate2 = xs.Average(), 
        // etc 
       }; 

Ma non ha senso provare a racchiuderlo in una query se si utilizza LINQ to Objects.Basta scrivere aggregati separatamente:

var aggregate1 = xs.Sum(); 
var aggregate2 = xs.Average(); 
// etc 

In altre parole:

var xs = from a in listOfAs 
     from b in a.Bs 
     select new { A=a, B=b }; 

var average1 = xs.Average(x => (x.B.Completed - x.A.Created).TotalSeconds); 
var average2 = xs.Average(x => (x.B.Completed - x.B.Created).TotalSeconds); 

ho capito bene?

Modifica: David B ha risposto allo stesso modo. Ho dimenticato di convertire TimeSpan in un semplice tipo numerico supportato dal metodo Average. Utilizzare TotalMilliseconds/Seconds/Minutes o qualunque sia la scala appropriata.

+0

Grazie, questo funziona perfettamente! Sono andato con l'opzione di aggregazione separata - questo rende le cose un po 'più chiare. –

1

consente di iniziare da ottenere il tempo medio tra partenza ed arrivo di B:

1) Innanzitutto è necessario la differenza di tempo tra l'inizio e la fine di ogni oggetto B così si dovrebbe fare questo:

List<TimeSpan> timeSpans = new List<TimeSpan>(); 
foreach (B myB in BList) 
{ 
    TimeSpan myTimeSpan = myB.CompletedDate.Subtract(myB.CreatedDate); 
    timeSpans.Add(myTimeSpan); 
} 

2) Ora è necessario ottenere la media di questo. È possibile farlo utilizzando le espressioni lambda:

//This will give you the average time it took to complete a task in minutes 
Double averageTimeOfB = timeSpans.average(timeSpan => timeSpan.TotalMinutes); 

È ora possibile fare la stessa cosa per ottenere il tempo medio tra l'inizio di A e la finitura di B come segue:

1) Ottenere la differenza di tempo per l'avvio di un la data e ciascuno di B di data di completamento:

List<TimeSpan> timeSpans_2 = new List<TimeSpan>(); 
foreach (B myB in BList) 
{ 
    TimeSpan myTimeSpan = myB.CompletedDate.Subtract(objectA.CreatedDate); 
    timeSpans_2.Add(myTimeSpan); 
} 

2) ottenere la media di questi differnces tempo esattamente come quello di cui sopra:

//This will give you the average time it took to complete a task in minutes 
Double averageTimeOfAandB = timeSpans_2.average(timeSpan => timeSpan.TotalMinutes); 

E il gioco è fatto. Spero che questo aiuti. Si prega di notare che questo codice non è stato testato.

MODIFICA: Ricorda che LINQ utilizza le espressioni Lamda ma è più di zucchero sintattico (non so molto sull'implementazione, quindi spero che tu non lo tenga contro di me). Inoltre, è possibile racchiudere le implementazioni fornite nei metodi in modo che sia possibile chiamarle più volte per un elenco di oggetti A.

+0

Grazie, questo farà davvero il lavoro. Tuttavia, dato che ho ottenuto 9 medie/min/max di tipo di cose da calcolare da questi dati, mi chiedevo se tutto ciò potesse essere fatto all'interno di una query di Linq, simile all'SQL originale. –

+0

oops, hai appena visto la tua modifica. Sembra che questo sarà il modo più semplice. Ho visto i metodi di media in Linq e ho pensato che potrebbero essere la via da seguire. Ma forse no. –

+0

Mi spiace essere un po 'confuso, vuoi dire che dato un oggetto di tipo A vuoi calcolare non solo la media ma anche il min/max..etc? – Draco

4

Sql e Linq differiscono su alcuni punti. Sql ha il raggruppamento implicito - prendere tutti questi e creare un gruppo. In linq, si può simulare il raggruppamento implicito introducendo una costante su cui raggruppare.

var query = 
    from i in Enumerable.Repeat(0, 1) 
    from a in AList 
    from b in a.BList 
    select new {i, a, b} into x 
    group x by x.i into g 
    select new 
    { 
    Avg1 = g.Average(x => (x.b.CompletedDate - x.a.CreatedDate) 
     .TotalMilliseconds), 
    Avg2 = g.Average(x => (x.b.CompletedDate - x.b.CreatedDate) 
     .TotalMilliseconds) 
    }; 
+0

Anche questo è ottimo, e alla fine potrei finire a usare questa implementazione. Se solo potessi avere due risposte! –