170 messaggi dal 17 febbraio 2009
Ciao a tutti,

ho creato una pagina asp.net che utilizza entity framework per prelevare dati dal DB SQLServer e visualizzarli nella pagina.
Prima di mostrare i risultati, ciclo con 2 for annidati per effettuare dei controlli....

Il problema è che se pubblico l'applicazione e la eseguo per la prima volta su IIS in locale ottendo dei risultati errati, mentre riaggiornando la pagina i risultati si allineano con quelli attesi.... ... ma è possibile una cosa del genere?? Sarà colpa della prima compilazione at runtime di asp.net?? I for annidati possono generare altri thred che sballano i risultati?
Non ho mai visto nulla del genere...

ecco un for annidato:

 Public Function GetAllSuccessCustomer() As List(Of Customer)
        Dim result As New List(Of Customer)
        For Each c As Customer In _ctx.CustomerTable
            Dim deviceOk As Integer = 0
            For Each d As Device In c.Device
                If d.Ack = True Then
                    Exit For
                End If
                If d.CurrentStatus.StatusEnum <> EnumDeviceStatus.Successful Then                      ' se il device è MailNotArrived non considero il cliente failed
                    Exit For
                Else
                    deviceOk = deviceOk + 1
                End If
                If deviceOk = c.Device.Count Then
                    result.Add(c)
                End If
            Next
        Next
        Return result
    End Function


Vi ringrazio per qualsiasi info! :)
Modificato da vailfox il 16 aprile 2012 17.30 -
11.886 messaggi dal 09 febbraio 2002
Contributi
ciao,

vailfox ha scritto:

Sarà colpa della prima compilazione at runtime di asp.net??

Può darsi che la tua applicazione necessiti di qualcosa che alla prima esecuzione non è stato ancora inizializzato. Dalla seconda richiesta in poi questa risorsa è disponibile e il funzionamento è quello corretto.

Comunque, congetture a parte, dovresti usare il Sql Profiler per esaminare le query SQL che la tua applicazione invia al database. Da lì puoi capire se le query che il provider di Entity Framework sono quelle che ti aspetti.

A proposito dei cicli for annidati... non li usare. Entity Framework, offrendoti un modello ad oggetti, ti può dare l'illusione che il modello relazionale dei database sia storia passata ma non è così: EF deve comunque assemblare delle query SQL e se il codice che scrivi non tiene conto di questo fatto puoi ritrovarti con un'applicazione che soffre di prestazioni basse ed effetti collaterali indesiderati.

Nel caso specifico, i cicli for annidati costringeranno EF ad inviare al database più query di quante ne siano necessarie. Questo è un problema noto come select n+1.

Ancora una volta, usa il Sql Profiler per renderti conto del dialogo che avviene tra l'applicazione e il database. Solo così puoi renderti conto di eventuali problemi e porvi rimedio.

La soluzione consiste nel riscrivere tutto il blocco di codice della funzione GetAllSuccessCustomer affinché usi una query LINQ.
Qui trovi un articolo di Stefano Mostarda per iniziare.
http://www.linqitalia.com/articoli/linq/introduzione-query-linq.aspx

Se ho capito quel che devi realizzare, si tratta di selezionare i Customer che abbiano ogni loro Device su Ack = False e CurrentStatus.StatusEnum = EnumDeviceStatus.Successful. Questo include anche quei Customer che non hanno alcun Device.

A proposito della condizione che coinvolge StatusEnum: EF ancora non supporta le enumerazioni quindi immagino che quella sia un wrapper per una proprietà numerica chiamata Status (sbaglio?).

Per ottenere migliori prestazioni, conviene comunque rinunciare all'uso dell'enumerazione e scrivere una query LINQ simile a questa (provala e vedi se funziona...)
Public Function GetAllSuccessCustomer() As List(Of Customer)
    Dim query = From c In _ctx.Customers.Include("Device")
                Where c.Device.All(Function(d) d.Ack = False And d.CurrentStatus.Status <> 0)
    Return query.ToList()
End Function

Per prima cosa, con l'extension method .Include dichiaro di voler usare la collezione Device correlata ai Customers. EF raccoglierà questo suggerimento (chiamato eager loading) e nel creare una query SQL effettuerà una JOIN tra le tabelle di Customer e Device.
L'extension method .All, invece, impone che tutti gli elementi della sequenza Device verifichino le due condizioni fornite.

ciao
Modificato da BrightSoul il 16 aprile 2012 22.51 -

Enjoy learning and just keep making
170 messaggi dal 17 febbraio 2009
Ciao BrightSoul,

nonostante l'ora voglio ringraziarti per l'esauriente risposta.
Con la promessa di scrivere in modo più chiaro domani, preciso che il mio Model è composto da Customer a cui possono appartenere più Device e per ogni device possono esservi più EventLog. Il CurrentStatus di Device e una property da me aggiunta tramite le partial class che seleziona l'ultimo EventLog corrispondente al Device in questione e dopo vari controlli lo restituisce come "stato corrente" del device stesso.
Ecco il codice (premetto che è una versione moolto ottimizabile).

http://pastebin.com/6v55LqYS

In sostanza, prima il current Status di Device e l'ultimo EventLog ricevuto se la data di quest'ultimo è inferiore alle ultime 24 ore, viceversa "faccio l'inject" di un fake EventLog con la property status settata a "Unknow".

ecco la partial class di EventLog con la property/wrapper

Public Enum EnumDeviceStatus
    Successful
    Failed
    NotBackedUp
    InProgress
    Unknown
    InvalidStatusValue
End Enum


Partial Public Class EventLog
    Public Property StatusEnum As EnumDeviceStatus
        Get
            Select Case Me.Status
                Case "Successful"
                    Return EnumDeviceStatus.Successful
                Case "Failed"
                    Return EnumDeviceStatus.Failed
                Case "NotBackedUp"
                    Return EnumDeviceStatus.NotBackedUp
                Case "Unknown"                          
                    Return EnumDeviceStatus.Unknown
                Case "InProgress"
                    Return EnumDeviceStatus.InProgress
                Case Else
                    Return EnumDeviceStatus.InvalidStatusValue
            End Select
        End Get
        Set(value As EnumDeviceStatus)
            Me.Status = value.ToString
        End Set
    End Property
End Class


In sostanza nel CustomerService devo restituire tutti i clienti che hanno i rispettivi (TUTTI) Device con CurrentStatus (e dunque EventLog con la property .StatusEnum = Successful).
E' un po un casino, devo fare un bel po di refactoring... ma non mi spiego come mai solo quando effettuo il deploy in locale ed eseguo la prima volta tramite IIS (IE9) pochi clienti, mentre se riaggiorno la pagina ottengo risultati diversi.... (ovvero il numero corretto di clienti).... imhò...
Modificato da vailfox il 16 aprile 2012 23.18 -
Modificato da vailfox il 16 aprile 2012 23.40 -
Modificato da vailfox il 16 aprile 2012 23.41 -
170 messaggi dal 17 febbraio 2009
questa implementazione di GetAllSuccessCustomer funziona, solo che non tengo conto degli status "unknow" dei device che precedentemente calcolavo mediante la property CurrentStatus (che non posso usare perché non supportata in Linq to SQL) ... mhò... :(

Public Function GetAllSuccessCustomer() As List(Of Customer)
        Dim query = _ctx.CustomerTable.Include("Device").Where(Function(c) c.Device.All(Function(d) d.Ack = False And d.EventLog.OrderByDescending(Function(k) k.ID).FirstOrDefault.Status = "Successful")).Select(Function(s) s)
        Return query.ToList()
    End Function

Modificato da vailfox il 17 aprile 2012 00.00 -
Modificato da vailfox il 17 aprile 2012 00.00 -
170 messaggi dal 17 febbraio 2009
Dunque, sembra abbia trovato la causa del problema... ovvero il LazyLoading (sembra strano anche a me :/ ).
Con il Profiler di VS2010 ho notato che vengono effettuato 286 query solo in una request della pagina .aspx in questione.
Ottimizzando le funzioni (che prima non brillavano in prestazioni ed eleganza) e caricando in EagerLoading tutte le entities tramite la query:

  Dim query = _ctx.CustomerTable.Include("Device.EventLog").Where(Function(c) c.Device.All(Function(d) d.Ack = False)).Select(Function(c) c)
Dim nonInAckCustomer = query.toList()


effetto un'altra query Linq2Object tra i risultati in questo modo:
Public Function GetAllSuccessCustomer() As List(Of Customer)
        LoadNonInAckCustomer()
        Dim list = From c As Customer In nonInAckCustomer Where c.Device.All(Function(d) d.CurrentStatus.StatusEnum = EnumDeviceStatus.Successful) Select c
        Return list.ToList
    End Function


Il risultato è che non ho più quel problema in fase di "first-compile" (permettetemi il termine) e che posso utilizzare la "customer" Property CurrentStatus implementata in Device tramite le partial class.

SOLO che attualmente caricare tutti i dati in memoria potrebbe portare ai dei problemi se i dati aumentano considerevolmente di dimensione nel tempo.
A me in realtà servirebbe caricare dal DB tutti i Customer con "l'inclusione" dei Device e "l'inclusione" di SOLO L'ULTIMO EventLog collegato al device....
Come fare ciò in Linq2Entities???
Modificato da vailfox il 17 aprile 2012 19.21 -
11.886 messaggi dal 09 febbraio 2002
Contributi
Ciao,

vailfox ha scritto:

SOLO che attualmente caricare tutti i dati in memoria potrebbe portare ai dei problemi se i dati aumentano considerevolmente di dimensione nel tempo.

sì, in effetti non è una soluzione che scala bene.
Ho visto il codice che hai postato su pastebin e pensavo che la logica contenuta nella proprietà CurrentStatus consistesse giusto in un paio di codice. Magari si riesce a snellirla e ad inserirla nella query LINQ.

vailfox ha scritto:

A me in realtà servirebbe caricare dal DB tutti i Customer con "l'inclusione" dei Device e "l'inclusione" di SOLO L'ULTIMO EventLog collegato al device....

Ok, proviamo a far questo per adesso anche se temo che, così facendo, la query SQL risultante cominci a complicarsi. Forse compariranno delle subqueries ma, ad ogni modo, osservala col Sql Profiler e misurane le prestazioni.

Dunque... dovendo esaminare una collezione (EventLog) annidata dentro un'altra collezione (Device) serviranno due istruzioni From l'una dentro l'altra.
Prova così, anche se non posso verificarne la correttezza.
Dim query = From c In _ctx.CustomerTable
            Where c.Device.All(Function(dev) dev.Ack = False)
            From d In c.Device
            Let ultimoEventLog = d.EventLog.OrderByDescending(Function(ev) ev.ID).FirstOrDefault()
            Select New With {.Customer = c, .Device = d, .EventLog = ultimoEventLog}

Ci siamo quasi... questa *dovrebbe* restituirti tutte e sole le informazioni di cui hai bisogno. Vedi che nell'istruzione Select decido io esplicitamente di proiettare un tipo anonimo contenente il Customer, il Device e l'EventLog, quindi non ho più bisogno di usare un Include in cima alla query.

Il risultato che questa query LINQ ti produce è un elenco di tanti elementi quanti sono i Device. Lato client, quindi, bisogna che si raggruppi per Customer. Forse c'è un sistema migliore ma in questo momento non mi viene in mente. La cosa non è neanche così immediata quindi trovare una soluzione ottimale necessita di un po' di studio.

ciao

Enjoy learning and just keep making
170 messaggi dal 17 febbraio 2009
Ti ringrazio per l'aiuto BrightSoul :)
Domani provo la tua query Linq.

Ho semplificato (e ottimizzato) la logica che sta dentro la property CurrentStatus di Device.
Attualmente l'applicazione funziona, ma mi sarebbe piaciuto capire perché attivando il LazyLoading in questo contesto ottengo quei problemi solo alla prima esecuzione della pagina... chissà se sia colpa di SQL Server Express 2008 R2 o di EntityFramework... bhò..
Ti faccio sapere l'esito della tua query!

Grazie ancora.
V.
170 messaggi dal 17 febbraio 2009
@BrightSoul

Dunque, ho provato la query linq, e FUNZIONA perfettamente! Grazie!
Ho dovuto però aggiungere un po di logica aggiuntiva perché la lista di Customer che devo elaborare deve essere strutturalmente uguale a quella di EF.
Dunque devo poter accedere ai Device e EventLog nel modo Customer.Device.EventLog.

per fare ciò, ho creato una nuova Lista di customer, a partire dalla lista ottenuta dal GropuBy sull'AnonymousType restituito dalla tua query.

ecco il codice.
http://pastebin.com/adyutfgD

Per il momento tutto funziona e le prestazioni con il Profiler di VS2010 mi da un tempo di 0.117/s nell'impiego della query (prima erano 0.350/s).
La pagina aspx è sensibilmente più veloce.

In oltre, ho dovuto usare un context diverso in quanto quello che uso di norma nella pagina ha il lazy loading abilitato.
Creandone un altro ho disabilitato il lazy evitando così, durante la lettura degli elementi dalla customerList, di leggere gli altri EventLog.

Cosa ne pensi BrightSoul? Consigli?
Grazie ancora per le dritte!

Torna al forum | Feed RSS

ASPItalia.com non è responsabile per il contenuto dei messaggi presenti su questo servizio, non avendo nessun controllo sui messaggi postati nei propri forum, che rappresentano l'espressione del pensiero degli autori.