Condividi tramite


Ottimizzazione delle prestazioni con Entity Framework 4.0 in un'applicazione Web ASP.NET 4

di Tom Dykstra

Questa serie di esercitazioni si basa sull'applicazione Web Contoso University creata dalla serie di esercitazioni Introduzione a Entity Framework 4.0 . Se non sono state completate le esercitazioni precedenti, come punto di partenza per questa esercitazione è possibile scaricare l'applicazione creata. È anche possibile scaricare l'applicazione creata dalla serie di esercitazioni completa. Per domande sulle esercitazioni, è possibile pubblicarle nel forum di Entity Framework ASP.NET.

Nell'esercitazione precedente è stato illustrato come gestire i conflitti di concorrenza. Questa esercitazione illustra le opzioni per migliorare le prestazioni di un'applicazione Web ASP.NET che usa Entity Framework. Verranno illustrati diversi metodi per ottimizzare le prestazioni o per diagnosticare i problemi di prestazioni.

Le informazioni presentate nelle sezioni seguenti sono probabilmente utili in un'ampia gamma di scenari:

  • Caricare in modo efficiente i dati correlati.
  • Gestire lo stato di visualizzazione.

Le informazioni presentate nelle sezioni seguenti potrebbero essere utili se si verificano singoli problemi di prestazioni:

  • Usare l'opzione di unione NoTracking.
  • Precompilare le query LINQ.
  • Esaminare i comandi di query inviati al database.

Le informazioni presentate nella sezione seguente sono potenzialmente utili per le applicazioni con modelli di dati estremamente grandi:

  • Generare in anticipo le visualizzazioni.

Annotazioni

Le prestazioni dell'applicazione Web sono influenzate da molti fattori, tra cui le dimensioni dei dati di richiesta e risposta, la velocità delle query di database, il numero di richieste che il server può accodare e la velocità con cui può essere usata e anche l'efficienza di qualsiasi libreria di script client che si sta usando. Se le prestazioni sono critiche nell'applicazione o se i test o l'esperienza indicano che le prestazioni dell'applicazione non sono soddisfacenti, è consigliabile seguire il protocollo normale per l'ottimizzazione delle prestazioni. Misurare per determinare dove si verificano i colli di bottiglia nelle prestazioni e quindi affrontare le aree che avranno il maggiore impatto sulle prestazioni complessive dell'applicazione.

Questo argomento è incentrato principalmente sui modi in cui è possibile migliorare le prestazioni in modo specifico di Entity Framework in ASP.NET. I suggerimenti qui riportati sono utili se si determina che l'accesso ai dati è uno dei colli di bottiglia delle prestazioni nell'applicazione. Ad eccezione di quanto indicato, i metodi illustrati qui non devono essere considerati "procedure consigliate" in generale, molti di essi sono appropriati solo in situazioni eccezionali o per affrontare tipi molto specifici di colli di bottiglia delle prestazioni.

Per avviare l'esercitazione, avviare Visual Studio e aprire l'applicazione Web Contoso University usata nell'esercitazione precedente.

Esistono diversi modi in cui Entity Framework può caricare i dati correlati nelle proprietà di navigazione di un'entità:

  • Lazy loading (caricamento differito). Quando un'entità viene letta per la prima volta, i dati correlati non vengono recuperati. Tuttavia, la prima volta che si tenta di accedere a una proprietà di navigazione, i dati necessari per tale proprietà di navigazione vengono recuperati automaticamente. Ciò comporta più query inviate al database, una per l'entità stessa e una ogni volta che devono essere recuperati i dati correlati per l'entità.

    Image05

Caricamento eager. Quando l'entità viene letta, i dati correlati vengono recuperati insieme a esso. Ciò in genere ha come risultato una query join singola che recupera tutti i dati necessari. È possibile specificare il caricamento eager usando il metodo Include, come si è già visto in questi tutorial.

Image07

  • Caricamento esplicito. È simile al caricamento differito, tranne che recuperi esplicitamente i dati correlati nel codice, non avviene automaticamente quando accedi a una proprietà di navigazione. I dati correlati vengono caricati manualmente usando il Load metodo della proprietà di navigazione per le raccolte oppure si utilizza il Load metodo della proprietà di riferimento per le proprietà che contengono un singolo oggetto. Ad esempio, è possibile chiamare il metodo PersonReference.Load per caricare la proprietà di navigazione Person di un'entità Department.

    Image06

Poiché non recuperano immediatamente i valori delle proprietà, il caricamento differito e il caricamento esplicito sono noti anche come caricamento posticipato.

Il Lazy Loading è il comportamento predefinito per un contesto di oggetti generato dal designer. Se si apre il file SchoolModel.Designer.cs che definisce la classe del contesto dell'oggetto, sono disponibili tre metodi del costruttore e ognuno include l'istruzione seguente:

this.ContextOptions.LazyLoadingEnabled = true;

In generale, se si conoscono i dati correlati per ogni entità recuperata, il caricamento eager offre le migliori prestazioni, perché una singola query inviata al database è in genere più efficiente rispetto alle query separate per ogni entità recuperata. D'altra parte, se è necessario accedere alle proprietà di navigazione di un'entità solo raramente o solo per un piccolo set di entità, il caricamento differita o il caricamento esplicito potrebbe essere più efficiente, perché il caricamento eager recupererebbe più dati di quanto necessario.

In un'applicazione web, il caricamento differito può avere comunque relativamente poco valore, perché le azioni dell'utente che richiedono dati correlati avvengono nel browser, che non ha alcuna connessione al contesto dell'oggetto che ha prodotto il rendering della pagina. D'altra parte, quando si esegue il databinding di un controllo, si conoscono generalmente i dati necessari, quindi è consigliabile scegliere il caricamento anticipato o il caricamento ritardato in base a ciò che è appropriato in ogni scenario.

Inoltre, un controllo databound potrebbe usare un oggetto entità dopo l'eliminazione del contesto dell'oggetto. In questo caso, un tentativo di caricamento differito di una proprietà di navigazione avrà esito negativo. Il messaggio di errore visualizzato è chiaro: "The ObjectContext instance has been disposed and can no longer be used for operations that require a connection."

Il controllo EntityDataSource disabilita il caricamento differito per impostazione predefinita. Per il ObjectDataSource controllo che stai usando per il tutorial corrente (o se accedi al contesto dell'oggetto dal codice della pagina), ci sono diversi modi per disabilitare il caricamento differito di default. È possibile disabilitarlo quando si crea un'istanza di un contesto di oggetto. Ad esempio, è possibile aggiungere la riga seguente al metodo del costruttore della SchoolRepository classe :

context.ContextOptions.LazyLoadingEnabled = false;

Per l'applicazione Contoso University, il contesto dell'oggetto disabiliterà automaticamente il caricamento differita in modo che questa proprietà non sia necessario impostare ogni volta che viene creata un'istanza di un contesto.

Aprire il modello di dati SchoolModel.edmx , fare clic sull'area di progettazione e quindi nel riquadro delle proprietà impostare la proprietà Lazy Loading Enabled su False. Salvare e chiudere il modello di dati.

Image04

Gestione dello stato di visualizzazione

Per fornire funzionalità di aggiornamento, una pagina Web ASP.NET deve archiviare i valori originali delle proprietà di un'entità quando viene eseguito il rendering di una pagina. Durante l'elaborazione postback il controllo può ricreare lo stato originale dell'entità e chiamare il metodo Attach dell'entità prima di applicare le modifiche e chiamare il metodo SaveChanges. Per impostazione predefinita, i controlli dati di Web Form di ASP.NET utilizzano il view state per archiviare i valori originali. Tuttavia, lo stato di visualizzazione può influire sulle prestazioni, perché è archiviato in campi nascosti che possono aumentare notevolmente le dimensioni della pagina inviata a e dal browser.

Le tecniche per la gestione dello stato di visualizzazione o alternative, come lo stato della sessione, non sono esclusive di Entity Framework, quindi questo tutorial non tratta l'argomento in dettaglio. Per altre informazioni, vedere i collegamenti alla fine dell'esercitazione.

Tuttavia, la versione 4 di ASP.NET offre un nuovo modo di usare lo stato di visualizzazione che ogni sviluppatore di applicazioni Web Form ASP.NET dovrebbe conoscere: la proprietà ViewStateMode. Questa nuova proprietà può essere impostata a livello di pagina o di controllo e consente di disabilitare lo stato di visualizzazione per impostazione predefinita per una pagina e abilitarlo solo per i controlli che lo richiedono.

Per le applicazioni in cui le prestazioni sono critiche, è consigliabile disabilitare sempre lo stato di visualizzazione a livello di pagina e abilitarlo solo per i controlli che lo richiedono. Le dimensioni dello stato di visualizzazione nelle pagine di Contoso University non verrebbero sostanzialmente ridotte con questo metodo, ma per vedere come funziona, eseguirai questa operazione per la pagina Instructors.aspx. La pagina contiene molti controlli, incluso un Label controllo con stato di visualizzazione disabilitato. Nessuno dei controlli in questa pagina deve effettivamente avere lo stato di visualizzazione abilitato. La DataKeyNames proprietà del GridView controllo specifica lo stato che deve essere mantenuto tra i postback, ma questi valori vengono mantenuti nello stato di controllo, che non è interessato dalla ViewStateMode proprietà .

La direttiva Page e il markup di controllo Label attualmente somigliano al seguente esempio:

<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true"
    CodeBehind="Instructors.aspx.cs" Inherits="ContosoUniversity.Instructors" %>
    ...
    <asp:Label ID="ErrorMessageLabel" runat="server" Text="" Visible="false" ViewStateMode="Disabled"></asp:Label> 
    ...

Apportare le modifiche seguenti:

  • Aggiungere ViewStateMode="Disabled" alla Page direttiva .
  • Rimuovere ViewStateMode="Disabled" dal controllo Label.

Il markup è ora simile all'esempio seguente:

<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true"
    CodeBehind="Instructors.aspx.cs" Inherits="ContosoUniversity.Instructors" 
    ViewStateMode="Disabled" %>
    ...
    <asp:Label ID="ErrorMessageLabel" runat="server" Text="" Visible="false"></asp:Label> 
    ...

Lo stato di visualizzazione è ora disabilitato per tutti i controlli. Se in un secondo momento si aggiunge un controllo che deve usare lo stato di visualizzazione, è sufficiente includere l'attributo ViewStateMode="Enabled" per tale controllo.

Uso dell'opzione di fusione NoTracking

Quando un contesto oggetto recupera le righe di database e crea oggetti entità che li rappresentano, per impostazione predefinita tiene traccia anche di tali oggetti entità usando il relativo gestore dello stato dell'oggetto. Questi dati di rilevamento fungono da cache e vengono usati quando si aggiorna un'entità. Poiché un'applicazione Web ha in genere istanze di contesto di oggetti di breve durata, le query spesso restituiscono dati che non devono essere rilevati, perché il contesto dell'oggetto che le legge verrà eliminato prima che una delle entità lette venga usata di nuovo o aggiornata.

In Entity Framework è possibile specificare se il contesto dell'oggetto tiene traccia degli oggetti entità impostando un'opzione di unione. È possibile impostare l'opzione di unione per le singole query o per i set di entità. Se lo si imposta per un set di entità, significa che si sta impostando l'opzione di unione predefinita per tutte le query create per tale set di entità.

Per l'applicazione Contoso University, il rilevamento non è necessario per nessuno dei set di entità a cui si accede dal repository, quindi è possibile impostare l'opzione di unione su NoTracking per tali set di entità quando si crea un'istanza del contesto dell'oggetto nella classe repository. Si noti che in questa esercitazione l'impostazione dell'opzione di unione non avrà un effetto significativo sulle prestazioni dell'applicazione. È probabile che l'opzione NoTracking renderà un miglioramento delle prestazioni osservabile solo in determinati scenari con volumi di dati elevati.

Nella cartella DAL aprire il file SchoolRepository.cs e aggiungere un metodo del costruttore che imposta l'opzione di unione per i set di entità a cui accede il repository:

public SchoolRepository()
{
    context.Departments.MergeOption = MergeOption.NoTracking;
    context.InstructorNames.MergeOption = MergeOption.NoTracking;
    context.OfficeAssignments.MergeOption = MergeOption.NoTracking;
}

Precompilazione delle query LINQ

La prima volta che Entity Framework esegue una query Entity SQL nella durata di una determinata ObjectContext istanza, la compilazione della query richiede tempo. Il risultato della compilazione viene memorizzato nella cache, il che significa che le esecuzioni successive della query sono molto più rapide. Le query LINQ seguono un modello simile, ad eccezione del fatto che alcune delle operazioni necessarie per compilare la query vengono eseguite ogni volta che viene eseguita la query. In altre parole, per le query LINQ, per impostazione predefinita non tutti i risultati della compilazione vengono memorizzati nella cache.

Se si dispone di una query LINQ che si prevede di eseguire ripetutamente nella vita di un contesto di oggetto, è possibile scrivere codice che causa la memorizzazione nella cache di tutti i risultati della compilazione la prima volta che viene eseguita la query LINQ.

Come illustrazione, questa operazione verrà eseguita per due Get metodi nella SchoolRepository classe , uno dei quali non accetta parametri (il GetInstructorNames metodo) e uno che richiede un parametro (il GetDepartmentsByAdministrator metodo ). Questi metodi in realtà non devono essere compilati perché non sono query LINQ:

public IEnumerable<InstructorName> GetInstructorNames()
{
    return context.InstructorNames.OrderBy("it.FullName").ToList();
}
public IEnumerable<Department> GetDepartmentsByAdministrator(Int32 administrator)
{
    return new ObjectQuery<Department>("SELECT VALUE d FROM Departments as d", context, MergeOption.NoTracking).Include("Person").Where(d => d.Administrator == administrator).ToList();
}

Tuttavia, in modo da poter provare le query compilate, procedere come se fossero state scritte come le query LINQ seguenti:

public IEnumerable<InstructorName> GetInstructorNames()
{
    return (from i in context.InstructorNames orderby i.FullName select i).ToList();
}
public IEnumerable<Department> GetDepartmentsByAdministrator(Int32 administrator)
{
    context.Departments.MergeOption = MergeOption.NoTracking;
    return (from d in context.Departments where d.Administrator == administrator select d).ToList();
}

È possibile modificare il codice in questi metodi in base a quanto illustrato in precedenza ed eseguire l'applicazione per verificare che funzioni prima di continuare. Tuttavia, le istruzioni seguenti consentono di creare versioni precompilata di esse.

Creare un file di classe nella cartella DAL , denominarlo SchoolEntities.cs e sostituire il codice esistente con il codice seguente:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Objects;

namespace ContosoUniversity.DAL
{
    public partial class SchoolEntities
    {
        private static readonly Func<SchoolEntities, IQueryable<InstructorName>> compiledInstructorNamesQuery =
            CompiledQuery.Compile((SchoolEntities context) => from i in context.InstructorNames orderby i.FullName select i);

        public IEnumerable<InstructorName> CompiledInstructorNamesQuery()
        {
            return compiledInstructorNamesQuery(this).ToList();
        }

        private static readonly Func<SchoolEntities, Int32, IQueryable<Department>> compiledDepartmentsByAdministratorQuery =
            CompiledQuery.Compile((SchoolEntities context, Int32 administrator) => from d in context.Departments.Include("Person") where d.Administrator == administrator select d);

        public IEnumerable<Department> CompiledDepartmentsByAdministratorQuery(Int32 administrator)
        {
            return compiledDepartmentsByAdministratorQuery(this, administrator).ToList();
        }
    }
}

Questo codice crea una classe parziale che estende la classe di contesto oggetto generata automaticamente. La classe parziale include due query LINQ compilate usando il Compile metodo della CompiledQuery classe . Crea anche metodi che è possibile usare per chiamare le query. Salvare e chiudere il file.

Successivamente, in SchoolRepository.cs, modificare i metodi esistenti GetInstructorNames e GetDepartmentsByAdministrator nella classe del repository in modo che chiamino le query compilate:

public IEnumerable<InstructorName> GetInstructorNames()
{
    return context.CompiledInstructorNamesQuery();
}
public IEnumerable<Department> GetDepartmentsByAdministrator(Int32 administrator)
{
    return context.CompiledDepartmentsByAdministratorQuery(administrator);
}

Eseguire la pagina Departments.aspx per verificare che funzioni come in precedenza. Il GetInstructorNames metodo viene chiamato per popolare l'elenco a discesa amministratore e il GetDepartmentsByAdministrator metodo viene chiamato quando si fa clic su Aggiorna per verificare che nessun insegnante sia un amministratore di più di un reparto.

Immagine03

Le query precompilate sono state predisposte nell'applicazione Contoso University solo per vedere come farlo, non perché migliorerebbero significativamente le prestazioni. La precompilazione delle query LINQ aggiunge un livello di complessità al codice, quindi è importante assicurarsi di farlo solo per le query che rappresentano effettivamente colli di bottiglia delle prestazioni nell'applicazione.

Esame delle query inviate al database

Quando si esaminano i problemi di prestazioni, a volte è utile conoscere i comandi SQL esatti che Entity Framework invia al database. Se si usa un IQueryable oggetto, un modo per eseguire questa operazione consiste nell'usare il ToTraceString metodo .

In SchoolRepository.cs modificare il codice nel GetDepartmentsByName metodo in modo che corrisponda all'esempio seguente:

public IEnumerable<Department> GetDepartmentsByName(string sortExpression, string nameSearchString)
{
    ...
    var departments = new ObjectQuery<Department>("SELECT VALUE d FROM Departments AS d", context).OrderBy("it." + sortExpression).Include("Person").Include("Courses").Where(d => d.Name.Contains(nameSearchString));
    string commandText = ((ObjectQuery)departments).ToTraceString();
    return departments.ToList();
}

È necessario eseguire il cast della departments variabile a un ObjectQuery tipo solo perché il Where metodo alla fine della riga precedente crea un IQueryable oggetto; senza il Where metodo , il cast non sarebbe necessario.

Impostare un punto di interruzione nella return riga e quindi eseguire la pagina Departments.aspx nel debugger. Quando si raggiunge il punto di interruzione, esaminare la commandText variabile nella finestra Variabili locali e usare il visualizzatore di testo (la lente di ingrandimento nella colonna Valore ) per visualizzarne il valore nella finestra Visualizzatore di testo . È possibile visualizzare l'intero comando SQL risultante da questo codice:

Image08

In alternativa, la funzionalità IntelliTrace in Visual Studio Ultimate consente di visualizzare i comandi SQL generati da Entity Framework che non richiedono di modificare il codice o persino impostare un punto di interruzione.

Annotazioni

È possibile eseguire le procedure seguenti solo se si dispone di Visual Studio Ultimate.

Ripristinare il codice originale nel GetDepartmentsByName metodo e quindi eseguire la pagina Departments.aspx nel debugger.

In Visual Studio selezionare il menu Debug , quindi IntelliTrace e quindi Eventi IntelliTrace.

Image11

Nella finestra IntelliTrace fare clic su Interrompi tutto.

Image12

Nella finestra IntelliTrace viene visualizzato un elenco di eventi recenti:

Image09

Fare clic sulla riga ADO.NET . Si espande per visualizzare il testo del comando:

Image10

È possibile copiare l'intera stringa di testo del comando negli Appunti dalla finestra Variabili locali .

Si supponga di usare un database con più tabelle, relazioni e colonne rispetto al database semplice School . Si potrebbe notare che una query che raccoglie tutte le informazioni necessarie in una singola Select istruzione contenente più Join clausole diventa troppo complessa per funzionare in modo efficiente. Si può passare dal caricamento anticipato al caricamento esplicito per semplificare la query.

Provare ad esempio a modificare il codice nel GetDepartmentsByName metodo in SchoolRepository.cs. Attualmente in tale metodo è disponibile una query oggetto con metodi per le proprietà di navigazione Person e Courses. Sostituire l'istruzione return con il codice che esegue il caricamento esplicito, come illustrato nell'esempio seguente:

public IEnumerable<Department> GetDepartmentsByName(string sortExpression, string nameSearchString)
{
    ...
    var departments = new ObjectQuery<Department>("SELECT VALUE d FROM Departments AS d", context).OrderBy("it." + sortExpression).Where(d => d.Name.Contains(nameSearchString)).ToList();
    foreach (Department d in departments)
    {
        d.Courses.Load();
        d.PersonReference.Load();
    }
    return departments;
}

Eseguire la pagina Departments.aspx nel debugger e controllare di nuovo la finestra IntelliTrace come in precedenza. Ora, dove c'era una singola query prima, viene visualizzata una lunga sequenza di esse.

Image13

Fare clic sulla prima riga ADO.NET per vedere cosa è successo alla query complessa visualizzata in precedenza.

Image14

La query dei dipartimenti è diventata una query semplice Select senza clausola Join, ma è seguita da query separate che recuperano corsi correlati e un amministratore, utilizzando un insieme di due query per ogni dipartimento restituito dalla query originale.

Annotazioni

Se lasci abilitato il caricamento differito, lo schema visualizzato qui, con la stessa query ripetuta molte volte, potrebbe essere il risultato del caricamento differito. Un modello da evitare è il caricamento ritardato dei dati correlati per ogni riga della tabella primaria. A meno che non si sia verificato che una singola query di join sia troppo complessa per essere efficiente, in genere è possibile migliorare le prestazioni in questi casi modificando la query principale per utilizzare il caricamento anticipato.

Generazione anticipata di visualizzazioni

Quando un ObjectContext oggetto viene creato per la prima volta in un nuovo dominio applicazione, Entity Framework genera un set di classi usate per accedere al database. Queste classi vengono chiamate viste e, se si dispone di un modello di dati molto grande, la generazione di queste visualizzazioni può ritardare la risposta del sito Web alla prima richiesta di una pagina dopo l'inizializzazione di un nuovo dominio applicazione. È possibile ridurre questo ritardo della prima richiesta creando le viste in fase di compilazione anziché in fase di esecuzione.

Annotazioni

Se l'applicazione non ha un modello di dati estremamente grande o se ha un modello di dati di grandi dimensioni, ma non si è interessati a un problema di prestazioni che influisce solo sulla prima richiesta di pagina dopo il riciclo di IIS, è possibile ignorare questa sezione. La creazione della visualizzazione non viene eseguita ogni volta che si crea un'istanza di un ObjectContext oggetto, perché le visualizzazioni vengono memorizzate nella cache nel dominio applicazione. Pertanto, a meno che non si stia riciclando di frequente l'applicazione in IIS, pochissime richieste di pagina potrebbero trarre vantaggio dalle visualizzazioni pregenerate.

È possibile pre-generare visualizzazioni usando lo strumento da riga di comando EdmGen.exe o un modello di Text Template Transformation Toolkit (T4). In questa esercitazione si userà un modello T4.

Nella cartella DAL aggiungere un file usando il modello Modello di testo (si trova nel nodo Generale nell'elenco Modelli installati ) e denominarlo SchoolModel.Views.tt. Sostituire il codice esistente nel file con il codice seguente:

<#
/***************************************************************************

Copyright (c) Microsoft Corporation. All rights reserved.

THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.

***************************************************************************/
#>

<#
    //
    // TITLE: T4 template to generate views for an EDMX file in a C# project
    //
    // DESCRIPTION:
    // This is a T4 template to generate views in C# for an EDMX file in C# projects.
    // The generated views are automatically compiled into the project's output assembly.
    //
    // This template follows a simple file naming convention to determine the EDMX file to process:
    // - It assumes that [edmx-file-name].Views.tt will process and generate views for [edmx-file-name].EDMX
    // - The views are generated in the code behind file [edmx-file-name].Views.cs
    //
    // USAGE:
    // Do the following to generate views for an EDMX file (e.g. Model1.edmx) in a C# project
    // 1. In Solution Explorer, right-click the project node and choose "Add...Existing...Item" from the context menu
    // 2. Browse to and choose this .tt file to include it in the project 
    // 3. Ensure this .tt file is in the same directory as the EDMX file to process 
    // 4. In Solution Explorer, rename this .tt file to the form [edmx-file-name].Views.tt (e.g. Model1.Views.tt)
    // 5. In Solution Explorer, right-click Model1.Views.tt and choose "Run Custom Tool" to generate the views
    // 6. The views are generated in the code behind file Model1.Views.cs
    //
    // TIPS:
    // If you have multiple EDMX files in your project then make as many copies of this .tt file and rename appropriately
    // to pair each with each EDMX file.
    //
    // To generate views for all EDMX files in the solution, click the "Transform All Templates" button in the Solution Explorer toolbar
    // (its the rightmost button in the toolbar) 
    //
#>
<#
    //
    // T4 template code follows
    //
#>
<#@ template language="C#" hostspecific="true"#>
<#@ include file="EF.Utility.CS.ttinclude"#>
<#@ output extension=".cs" #>
<# 
    // Find EDMX file to process: Model1.Views.tt generates views for Model1.EDMX
    string edmxFileName = Path.GetFileNameWithoutExtension(this.Host.TemplateFile).ToLowerInvariant().Replace(".views", "") + ".edmx";
    string edmxFilePath = Path.Combine(Path.GetDirectoryName(this.Host.TemplateFile), edmxFileName);
    if (File.Exists(edmxFilePath))
    {
        // Call helper class to generate pre-compiled views and write to output
        this.WriteLine(GenerateViews(edmxFilePath));
    }
    else
    {
        this.Error(String.Format("No views were generated. Cannot find file {0}. Ensure the project has an EDMX file and the file name of the .tt file is of the form [edmx-file-name].Views.tt", edmxFilePath));
    }
    
    // All done!
#>

<#+
    private String GenerateViews(string edmxFilePath)
    {
        MetadataLoader loader = new MetadataLoader(this);
        MetadataWorkspace workspace;
        if(!loader.TryLoadAllMetadata(edmxFilePath, out workspace))
        {
            this.Error("Error in the metadata");
            return String.Empty;
        }
            
        String generatedViews = String.Empty;
        try
        {
            using (StreamWriter writer = new StreamWriter(new MemoryStream()))
            {
                StorageMappingItemCollection mappingItems = (StorageMappingItemCollection)workspace.GetItemCollection(DataSpace.CSSpace);

                // Initialize the view generator to generate views in C#
                EntityViewGenerator viewGenerator = new EntityViewGenerator();
                viewGenerator.LanguageOption = LanguageOption.GenerateCSharpCode;
                IList<EdmSchemaError> errors = viewGenerator.GenerateViews(mappingItems, writer);

                foreach (EdmSchemaError e in errors)
                {
                    // log error
                    this.Error(e.Message);
                }

                MemoryStream memStream = writer.BaseStream as MemoryStream;
                generatedViews = Encoding.UTF8.GetString(memStream.ToArray());
            }
        }
        catch (Exception ex)
        {
            // log error
            this.Error(ex.ToString());
        }

        return generatedViews;
    }
#>

Questo codice genera visualizzazioni per un file con estensione edmx che si trova nella stessa cartella del modello e con lo stesso nome del file modello. Ad esempio, se il file modello è denominato SchoolModel.Views.tt, cercherà un file del modello di dati denominato SchoolModel.edmx.

Salvare il file, quindi fare clic con il pulsante destro del mouse sul file in Esplora soluzioni e scegliere Esegui strumento personalizzato.

Immagine02

Visual Studio genera un file di codice che crea le visualizzazioni denominate SchoolModel.Views.cs in base al modello. Si potrebbe aver notato che il file di codice viene generato anche prima di selezionare Esegui strumento personalizzato, non appena si salva il file modello.

Immagine01

È ora possibile eseguire l'applicazione e verificare che funzioni come in precedenza.

Per altre informazioni sulle viste pregenerate, vedere le risorse seguenti:

Questa operazione completa l'introduzione al miglioramento delle prestazioni in un'applicazione Web ASP.NET che usa Entity Framework. Per ulteriori informazioni, vedi le seguenti risorse:

L'esercitazione successiva esamina alcuni dei miglioramenti importanti di Entity Framework che sono nuovi nella versione 4.