Maximización del rendimiento con Entity Framework 4.0 en una aplicación web de ASP.NET 4

De Tom Dykstra

Esta serie de tutoriales se basa en la aplicación web de Contoso University creada por la serie de tutoriales Introducción a Entity Framework 4.0. Si no completó los tutoriales anteriores, como punto de partida de este tutorial, puede descargar la aplicación que habría creado. También puede descargar la aplicación creada por la serie de tutoriales completa. Si tiene preguntas sobre los tutoriales, puede publicarlas en el foro de ASP.NET Entity Framework.

En el tutorial anterior verá cómo tratar los conflictos de simultaneidad. En este tutorial se muestran las opciones para mejorar el rendimiento en una aplicación web de ASP.NET que use Entity Framework. Aprenderá varios métodos para maximizar el rendimiento o diagnosticar problemas de rendimiento.

Es probable que la información presentada en las secciones siguientes sea útil en una amplia variedad de escenarios:

  • Carga eficaz de datos relacionados.
  • Administración del estado de la vista.

La información presentada en las secciones siguientes puede ser útil si tiene consultas individuales que presentan problemas de rendimiento:

  • Use la opción de combinación NoTracking.
  • Compile previamente las consultas LINQ.
  • Examine los comandos de consulta enviados a la base de datos.

La información presentada en la sección siguiente es potencialmente útil para las aplicaciones que tienen modelos de datos extremadamente amplios:

  • Generación previa de vistas.

Nota:

El rendimiento de las aplicaciones web se ve afectado por muchos factores, como el tamaño de los datos de solicitud y respuesta, la velocidad de las consultas de base de datos, el número de solicitudes que el servidor puede poner en cola y la rapidez con la que puede atenderlas, e incluso la eficacia de cualquier biblioteca de scripts de cliente que pueda usar. Si el rendimiento es crítico en la aplicación o si las pruebas o la experiencia muestran que el rendimiento de la aplicación no es satisfactorio, debe seguir el protocolo normal para la optimización del rendimiento. Mida para determinar dónde se producen los cuellos de botella de rendimiento y luego abordar las áreas que tendrán el mayor impacto en el rendimiento general de las aplicaciones.

Este tema se centra principalmente en las formas en las que puede mejorar potencialmente el rendimiento específicamente de Entity Framework en ASP.NET. Estas sugerencias son útiles si determina que el acceso a los datos es uno de los cuellos de botella de rendimiento de la aplicación. A excepción de lo indicado, los métodos que se explican aquí no deben considerarse "procedimientos recomendados" en general. Muchos de ellos solo son adecuados en situaciones excepcionales o para abordar tipos de cuellos de botella de rendimiento muy específicos.

Para iniciar el tutorial, inicie Visual Studio y abra la aplicación web de Contoso University con la que estaba trabajando en el tutorial anterior.

Hay varias formas con las que Entity Framework carga datos relacionados en las propiedades de navegación de una entidad:

  • Carga diferida. Cuando la entidad se lee por primera vez, no se recuperan datos relacionados. Pero la primera vez que intente obtener acceso a una propiedad de navegación, se recuperan automáticamente los datos necesarios para esa propiedad de navegación. Esto da como resultado varias consultas enviadas a la base de datos: una para la propia entidad y otra cada vez que se deben recuperar los datos relacionados de la entidad.

    Image05

Carga diligente. Cuando se lee la entidad, junto a ella se recuperan datos relacionados. Esto normalmente da como resultado una única consulta de combinación en la que se recuperan todos los datos que se necesitan. Puede especificar la carga diligente mediante el método Include, como ya ha visto en estos tutoriales.

Image07

  • Carga explícita. Esto es similar a la carga diferida, salvo que recupera explícitamente los datos relacionados en el código; no se produce automáticamente cuando se accede a una propiedad de navegación. Los datos relacionados se cargan manualmente mediante el método Load de la propiedad de navegación para colecciones o se usa el método Load de la propiedad de referencia para las propiedades que contienen un único objeto. (Por ejemplo, se llama al método PersonReference.Load para cargar la propiedad Person de navegación de una entidad Department).

    Image06

Dado que no recuperan inmediatamente los valores de propiedad, la carga diferida y la carga explícita también se conocen como carga diferida.

La carga diferida es el comportamiento predeterminado de un contexto de objeto generado por el diseñador. Si abre el archivo SchoolModel.Designer.cs que define la clase de contexto de objeto, encontrará tres métodos de constructor y cada uno de ellos incluye la siguiente instrucción:

this.ContextOptions.LazyLoadingEnabled = true;

En general, si sabe que necesita datos relacionados para cada entidad que se recupere, la carga diligente ofrece el mejor rendimiento, dado que una única consulta que se envía a la base de datos normalmente es más eficaz que consultas independientes para cada entidad recuperada. Por otro lado, si necesita acceder a las propiedades de navegación de una entidad solo con poca frecuencia o únicamente para un pequeño conjunto de entidades, la carga diferida o la carga explícita puede ser más eficaz, ya que la carga diligente recuperaría más datos de los que necesita.

En una aplicación web, la carga diferida puede tener un valor relativamente pequeño, ya que las acciones del usuario que afectan a la necesidad de datos relacionados tienen lugar en el explorador, que no tiene conexión con el contexto de objeto que ha representado la página. Por otro lado, al enlazar datos a un control, normalmente sabe qué datos necesita y, por lo tanto, es mejor elegir la carga diligente o la carga diferida en función de lo que sea adecuado en cada escenario.

Además, un control de entrada de datos podría usar un objeto de entidad después de eliminar el contexto del objeto. En ese caso, se produciría un error al intentar una carga diferida de una propiedad de navegación. El mensaje de error que recibe es claro: «The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.»

El control EntityDataSource deshabilita la carga diferida de forma predeterminada. En el control ObjectDataSource que usa para el tutorial actual (o si accede al contexto de objeto desde el código de página), hay varias maneras de deshabilitar la carga diferida de forma predeterminada. Puede deshabilitarla al crear una instancia de un contexto de objeto. Por ejemplo, puede agregar la siguiente línea al método constructor de la clase SchoolRepository:

context.ContextOptions.LazyLoadingEnabled = false;

Con la aplicación de Contoso University, hará que el contexto del objeto deshabilite automáticamente la carga diferida para que esta propiedad no tenga que establecerse siempre que se cree una instancia de un contexto.

Abra el modelo de datos SchoolModel.edmx, seleccione la superficie de diseño y luego en el panel de propiedades, establezca la propiedad Lazy Loading Enabled en False. Guarde y cierre el modelo de datos.

Image04

Administración del estado de la vista

Para proporcionar la funcionalidad de actualización, una página web de ASP.NET debe almacenar los valores de propiedad originales de una entidad cuando se representa una página. Durante el procesamiento de postback, el control puede volver a crear el estado original de la entidad y llamar al método Attach de la entidad antes de aplicar los cambios y llamar al método SaveChanges. De forma predeterminada, los controles de datos de ASP.NET Web Forms usan el estado de vista para almacenar los valores originales. Pero el estado de vista puede afectar al rendimiento, ya que se almacena en campos ocultos que pueden aumentar considerablemente el tamaño de la página que se envía al explorador y desde él.

Las técnicas para administrar el estado de vista o alternativas, como el estado de sesión, no son exclusivas de Entity Framework, por lo que este tutorial no profundiza este tema. Para obtener más información, consulte los vínculos al final de este documento.

Pero la versión 4 de ASP.NET proporciona una nueva forma de trabajar con estado de vista que todos los desarrolladores de ASP.NET de aplicaciones de Web Forms deben tener en cuenta: la propiedad ViewStateMode. Esta nueva propiedad se puede establecer en el nivel de página o control y le permite deshabilitar el estado de vista de forma predeterminada para una página y habilitarla solo para los controles que lo necesitan.

En el caso de las aplicaciones en las que el rendimiento es crítico, una buena práctica consiste en deshabilitar siempre el estado de vista al nivel de la página y habilitarlo solo para los controles que lo requieran. El tamaño del estado de vista en las páginas de Contoso University no se reduciría considerablemente con este método, pero para ver cómo funciona, lo hará para la página de Instructors.aspx. Esa página contiene muchos controles, incluido un control Label que tiene el estado de vista deshabilitado. En realidad ninguno de los controles de esta página debe tener habilitado el estado de vista. (La propiedad DataKeyNames del control GridView especifica el estado que se debe mantener entre postbacks, pero estos valores se mantienen en el estado de control, que no se ve afectado por la propiedad ViewStateMode).

La directiva Page y el marcado de control Label se asemejan actualmente al ejemplo siguiente:

<%@ 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> 
    ...

Haga los siguientes cambios:

  • Agregue ViewStateMode="Disabled" a la directiva Page.
  • Quite ViewStateMode="Disabled" del control Label.

El marcado ahora es similar al ejemplo siguiente:

<%@ 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> 
    ...

El estado de vista ahora está deshabilitado para todos los controles. Si posteriormente agrega un control que necesita usar el estado de vista, lo único que debe hacer es incluir el atributo ViewStateMode="Enabled" para ese control.

Uso de la opción Combinación sin seguimiento

Cuando un contexto de objeto recupera filas de base de datos y crea objetos de entidad que los representan, de forma predeterminada también hace un seguimiento de esos objetos de entidad mediante su administrador de estado de objetos. Los datos de seguimiento actúan como una caché y se usan cuando se actualiza una entidad. Dado que una aplicación web normalmente tiene instancias de contexto de objeto de corta duración, las consultas suelen devolver datos que no se necesitan seguir, ya que el contexto del objeto que los lee se eliminará antes de que se use de nuevo o se actualice cualquiera de las entidades que lee.

En Entity Framework, puede especificar si el contexto de objeto hace un seguimiento de los objetos de entidad estableciendo una opción de combinación. Puede establecer la opción de combinación para consultas individuales o para conjuntos de entidades. Si la establece para un conjunto de entidades, significa que va a establecer la opción de combinación predeterminada para todas las consultas que se crean para ese conjunto de entidades.

Para la aplicación de Contoso University, el seguimiento no es necesario para ninguno de los conjuntos de entidades a los que se accede desde el repositorio, por lo que puede establecer la opción de combinación en NoTracking para esos conjuntos de entidades al crear una instancia del contexto de objeto en la clase de repositorio. (Tenga en cuenta que, en este tutorial, establecer la opción de combinación no tendrá un efecto perceptible en el rendimiento de la aplicación. Es probable que la opción NoTracking mejore el rendimiento observable solo en determinados escenarios de gran volumen de datos).

En la carpeta DAL, abra el archivo SchoolRepository.cs y agregue un método de constructor que establezca la opción de combinación para los conjuntos de entidades a los que accede el repositorio:

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

Compilación previa de consultas LINQ

La primera vez que Entity Framework ejecuta una consulta de Entity SQL durante la vigencia de una instancia determinada ObjectContext, se tarda algún tiempo en compilar la consulta. El resultado de la compilación se almacena en caché, lo que significa que las ejecuciones posteriores de la consulta son mucho más rápidas. Las consultas LINQ siguen un patrón similar, salvo que parte del trabajo necesario para compilar la consulta se haga cada vez que se ejecuta la consulta. Es decir, para las consultas LINQ, de forma predeterminada no todos los resultados de la compilación se almacenan en caché.

Si tiene una consulta LINQ que espera ejecutar repetidamente mientras dure un contexto de objeto, puede escribir código que haga que todos los resultados de la compilación se almacenen en caché la primera vez que se ejecute la consulta LINQ.

Como ilustración, lo hará para dos métodos Get de la clase SchoolRepository, uno de los cuales no toma ningún parámetro (el método GetInstructorNames) y otro que requiere un parámetro (el método GetDepartmentsByAdministrator). Estos métodos, tal y como están ahora, en realidad no necesitan compilarse porque no son consultas 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();
}

Sin embargo, para poder probar las consultas compiladas, continuará como si se hubieran escrito como las siguientes consultas LINQ:

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();
}

Puede cambiar el código de estos métodos a lo que se muestra anteriormente y ejecutar la aplicación para comprobar que funciona antes de continuar. Pero las instrucciones siguientes saltan directamente a la creación de versiones suyas compiladas previamente.

Cree un nuevo archivo de clase en la carpeta DAL, asígnele el nombre SchoolEntities.cs, y reemplace el código existente por el siguiente:

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();
        }
    }
}

Este código crea una clase parcial que extiende la clase de contexto de objeto generada automáticamente. La clase parcial incluye dos consultas LINQ compiladas mediante el método Compile de la clase CompiledQuery. También crea métodos que puede usar para llamar a las consultas. Guarde y cierre el archivo.

Luego en SchoolRepository.cs, cambie los métodos GetInstructorNames y GetDepartmentsByAdministrator existentes de la clase de repositorio para que llamen a las consultas compiladas:

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

Ejecute la página Departments.aspx para comprobar que funciona como antes. Se llama al método GetInstructorNames para rellenar la lista desplegable del administrador y se llama al método GetDepartmentsByAdministrator al hacer clic en Actualizar para comprobar que ningún instructor es administrador de más de un departamento.

Image03

Ha compilado consultas compiladas previamente en la aplicación de Contoso University solo para ver cómo hacerlo, no porque mejoraría considerablemente el rendimiento. La compilación previa de consultas LINQ agrega un nivel de complejidad al código, por lo que debe asegurarse de hacerlo solo para las consultas que realmente representan cuellos de botella de rendimiento en la aplicación.

Examen de consultas enviadas a la base de datos

Al investigar problemas de rendimiento, a veces resulta útil conocer los comandos exactos de SQL que Entity Framework envía a la base de datos. Si está trabajando con un objeto IQueryable, una manera de hacerlo es usar el método ToTraceString.

En SchoolRepository.cs, cambie el código del método GetDepartmentsByName para que coincida con el ejemplo siguiente:

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();
}

La variable departments debe convertirse en un tipo ObjectQuery solo porque el método Where al final de la línea anterior crea un objeto IQueryable; sin el método Where, la conversión no sería necesaria.

Establezca un punto de interrupción en la línea return y luego ejecute la página Departments.aspx en el depurador. Al alcanzar el punto de interrupción, examine la variable commandText en la ventana Locales y use el visualizador de texto (la lupa en la columna Valor) para mostrar su valor en la ventana Visualizador de texto. Puede ver el comando SQL completo resultante de este código:

Image08

Como alternativa, la característica IntelliTrace de Visual Studio Ultimate proporciona una manera de ver los comandos SQL generados por Entity Framework que no requieren que cambie el código ni que establezca un punto de interrupción.

Nota:

Solo puede hacer los procedimientos siguientes si tiene Visual Studio Ultimate.

Restaure el código original en el método GetDepartmentsByName y luego ejecute la página Departments.aspx en el depurador.

En Visual Studio, seleccione el menú Depurar, IntelliTrace y luego Eventos de IntelliTrace.

Image11

En la ventana IntelliTrace seleccione Interrumpir todo.

Image12

La ventana IntelliTrace muestra una lista de eventos recientes:

Image09

Haga clic en la línea ADO.NET. Se expande para mostrar el texto del comando:

Image10

Puede copiar la cadena de texto completa del comando en el Portapapeles desde la ventana Locales.

Supongamos que estaba trabajando con una base de datos con más tablas, relaciones y columnas que la base de datos simple School. Es posible que una consulta que recopile toda la información necesaria en una única instrucción Select que contenga varias cláusulas Join sea demasiado compleja para funcionar de forma eficaz. En ese caso, puede cambiar la carga diligente por la carga explícita para simplificar la consulta.

Por ejemplo, intente cambiar el código del método GetDepartmentsByName en SchoolRepository.cs. Actualmente, en ese método tiene una consulta de objeto que tiene métodos Include para las propiedades de navegación Person y Courses. Reemplace la instrucción return por código que hace la carga explícita, como se muestra en el ejemplo siguiente:

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;
}

Ejecute la página Departments.aspx en el depurador y vuelva a comprobar la ventana IntelliTrace como hizo antes. Donde había una única consulta, ahora verá una larga secuencia.

Image13

Haga clic en la primera línea ADO.NET para ver lo que ha ocurrido con la consulta compleja que ha visto anteriormente.

Image14

La consulta de departamentos se ha convertido en una consulta Select sencilla sin cláusula Join, pero va seguida de consultas independientes que recuperan cursos relacionados y un administrador, mediante un conjunto de dos consultas para cada departamento devuelto por la consulta original.

Nota:

Si deja habilitada la carga diferida, el patrón que se ve aquí, con la misma consulta repetida muchas veces, puede resultar de la carga diferida. Un patrón que normalmente querría evitar es la carga diferida de datos relacionados con cada fila de la tabla principal. A menos que haya comprobado que una única consulta de combinación es demasiado compleja para ser eficaz, normalmente podría mejorar el rendimiento en tales casos cambiando la consulta principal para usar la carga diligente.

Generación de vistas previamente

Cuando se crea un objeto ObjectContext por primera vez en un nuevo dominio de aplicación, Entity Framework genera un conjunto de clases que usa para acceder a la base de datos. Estas clases se denominan vistas y, si tiene un modelo de datos muy grande, la generación de estas vistas puede retrasar la respuesta del sitio web a la primera solicitud de una página después de inicializar un nuevo dominio de aplicación. Puede reducir este retraso de primera solicitud creando las vistas en tiempo de compilación en lugar de en tiempo de ejecución.

Nota:

Si la aplicación no tiene un modelo de datos extremadamente grande o si tiene un modelo de datos grande, pero no le preocupa un problema de rendimiento que afecte solo a la primera solicitud de página después de reciclar IIS, puede omitir esta sección. La creación de vistas no se produce cada vez que se crea una instancia de un objeto ObjectContext, ya que las vistas se almacenan en caché en el dominio de aplicación. Por lo tanto, a menos que esté reciclando la aplicación con frecuencia en IIS, muy pocas solicitudes de página se beneficiarían de las vistas generadas previamente.

Puede generar previamente vistas mediante la herramienta de línea de comandos EdmGen.exe o mediante una plantilla de Text Template Transformation Toolkit (T4). En este tutorial se usará una plantilla T4.

En la carpeta DAL, agregue un archivo mediante la plantilla Plantilla de texto, (se encuentra en el nodo General de la lista Plantillas instaladas) y asígnele el nombre SchoolModel.Views.tt. Reemplace el código del archivo existente por el código siguiente:

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

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;
    }
#>

Este código genera vistas para un archivo .edmx que se encuentra en la misma carpeta que la plantilla y que tiene el mismo nombre que el archivo de plantilla. Por ejemplo, si el archivo de plantilla se denomina SchoolModel.Views.tt, buscará un archivo de modelo de datos denominado SchoolModel.edmx.

Guarde el archivo y haga clic con el botón derecho en él en Explorador de solucionesy seleccione Ejecutar herramienta personalizada.

Image02

Visual Studio genera un archivo de código que crea las vistas, que se denomina SchoolModel.Views.cs en función de la plantilla. (Es posible que haya observado que el archivo de código se genera incluso antes de seleccionar Ejecutar herramienta personalizada, en cuanto guarda el archivo de plantilla).

Image01

Ahora puede ejecutar la aplicación y comprobar que funciona como antes.

Para obtener más información sobre las vistas generadas previamente, consulte los siguientes recursos:

Esto completa la introducción a la mejora del rendimiento en una aplicación web de ASP.NET que usa Entity Framework. Para obtener más información, consulte los siguientes recursos:

En el siguiente tutorial se revisan algunas de las mejoras importantes de Entity Framework que son nuevas en la versión 4.