Compartir a través de


Puntos de datos

Juego con la versión alfa de EF6

Julie Lerman

 

No hay nada como un juguete nuevo y aunque las versiones compiladas nocturnas de Entity Framework 6 (EF6) se pueden descargar durante todo el proceso evolutivo, esperé hasta el lanzamiento de la primera versión alfa (que salió el 30 de octubre de 2012) para poner manos a la obra y empezar a jugar.

Si se ahora se pregunta: “¿Cómo? ¿Versiones compiladas nocturnas?” es probable que no se haya enterado de la noticia de que después del lanzamiento de EF5, Entity Framework se convirtió en un proyecto de código abierto y las versiones subsiguientes se desarrollarán en forma abierta (y comunitaria) en entityframework.codeplex.com. Hace poco escribí un artículo en mi blog: “Oriéntese en el sitio de CodePlex del proyecto de código abierto Entity Framework” (bit.ly/W9eqZS): le recomiendo que lo revise antes de visitar las páginas de CodePlex.

La versión nueva de EF será muchísimo más flexible y extensible. A mi juicio, las tres características más importantes que se incorporarán en la versión 6 de EF son:

  1. El procedimiento y la función almacenados en Code First.
  2. La compatibilidad con el patrón async/await de .NET 4.5.
  3. Las API centrales de Entity Framework que actualmente residen dentro de Microsoft .NET Framework.

Este último punto no solo resulta en la compatibilidad para los tipos de enumeración y datos espaciales en las aplicaciones para .NET Framework 4, sino también significa que, como el código de EF es abierto, todo EF ahora se beneficia de tener el código abierto.

Y aunque quizás no cuenten con el gran atractivo de estas tres características, también hay otras funciones importantes que vienen en camino. Por ejemplo:

  • Las convenciones Code First personalizadas que se descartaron antes del lanzamiento de EF4.1 ahora están en EF6, con diversas formas para implementarlas.
  • Las migraciones de Code First permiten bases de datos con varios esquemas.
  • Podemos definir configuraciones de Entity Framework en el código en vez de configurarlas en un archivo web.config o app.config (lo que puede ser complicado).
  • La configuración en código fue posible gracias a las nuevas funciones de extensibilidad con solucionadores de dependencias.
  • Podemos personalizar la forma en que Code First crea la tabla _Migrations­History, para adaptarla a una gama de proveedores de bases de datos.
  • Las herramientas de EF Power Tools están recibiendo mejoras y se están incorporando en Visual Studio EF Designer. Uno de estos cambios ofrecerá un camino mejor para elegir un flujo de trabajo modelo, Code First incluido.

Cómo obtener EF6 Alpha

Los desarrolladores puristas probablemente estarán interesados en descargar las versiones compiladas nocturnas. Si prefiere usar los paquetes publicados, entonces el paquete de NuGet permite una experiencia de instalación sin contratiempos. Use el Administrador de paquetes de NuGet y seleccione “Incluir versión preliminar” para descargar el paquete de EF6. Si realiza la instalación mediante la Consola del administrador de paquetes, no olvide agregar “-prerelease” al final del comando install-package.

Tenga en cuenta que la versión del 10 de diciembre de 2012 que empleé aquí (versión de archivo 6.0.11025.0 y versión de producto 6.0.0-alpha2-11210) no incluye la compatibilidad de procedimiento o función almacenada ni la consolidación de herramientas. Y como esto es una versión alfa muy temprana, me imagino que algunos detalles de estas características cambiarán en las versiones posteriores. Si bien los conceptos generales quedarán inalterados, es probable que parte de la sintaxis u otros pormenores evolucionen, de acuerdo con las respuestas de la comunidad. Gracias al ejercicio que realicé en esta columna, también pude aportar algunos comentarios.

Patrón asincrónico de .NET 4.5 en EF6

El uso de procesos asincrónicos en .NET Framework para evitar los bloqueos cuando se espera la devolución de datos ha intimidado a más de un desarrollador, sobre todo con el uso aplicaciones desconectadas o bases de datos remotas. En .NET Framework 2.0 se introdujo el proceso Background Worker, pero siguió siendo muy complejo. ADO.NET 2.0 alivió las cosas un poco con los métodos como BeginExecuteQuery y EndExecuteQuery, pero Entity Framework jamás contó con nada parecido. Una de las principales características que se incorporó en .NET Framework 4.5 fue el nuevo patrón asincrónico que permite esperar los resultados de los métodos que se definieron como asincrónicos, lo que simplifica radicalmente los procesos asincrónicos.

En EF6 se agregó una gran cantidad de métodos que funcionan con el patrón asincrónico de .NET 4.5. En concordancia con las pautas para el patrón nuevo, todos los métodos nuevos tienen nombres que terminan con Async: por ejemplo SaveChangesAsync, FindAsync y Execute­SqlCommandAsync. Para LINQ to Entities, un espacio de nombres nuevo, llamado System.Data.Entity.IQueryableExtensions, contiene las versiones asincrónicas de la gran cantidad de métodos de LINQ: por ejemplo ToListAsync, First­Or­DefaultAsync, MaxAsync y SumAsync. Y para cargar en forma explícita los datos desde las entidades administradas por un DbContext, ahora existe LoadAsync.

Lo que sigue es un pequeño experimento que conduje con esta función; primero sin usar los métodos asincrónicos y luego con los mismos. Le recomiendo encarecidamente que estudie el nuevo patrón asincrónico. “Programación asincrónica con async y await (C# y Visual Basic)”, disponible en bit.ly/U8FzhP, es un buen punto de partida.

Mi ejemplo contiene una clase Casino que incluye una calificación para el casino. Creé un método para un repositorio que encontrará un casino específico, aumentará la calificación con mi método UpdateRating (que no tiene importancia para esta explicación y, por lo tanto, se omite) y vuelve a guardar el cambio en la base de datos:

public void IncrementCasinoRating(int id)
{
  using (var context = new CasinoSlotsModel())
  {
    var casino =  context.Casinos.Find(id);
    UpdateRating(casino);
    context.SaveChanges();
  }
}

Este método contiene dos puntos donde un subproceso se puede bloquear. El primero se encuentra en la llamada a Find, que provoca que el contexto busque el casino solicitado en la memoria caché y consulte la base de datos cuando no lo encuentra. El segundo se produce cuando se solicita al contexto que vuelva a guardar los datos modificados en la base de datos.

Estructuré el código de la interfaz de usuario solo para ilustrar el comportamiento pertinente. La interfaz de usuario incluye un método que llama al método IncrementCasinoRating y, al terminar, escribe una notificación en la consola:

private static void UI_RequestIncrement (SimpleRepository repo)
{
  repo.IncrementCasinoRating(1);
  Console.WriteLine("Synchronous Finish ");
}

En otro método gatillo la prueba al llamar UI_ Increment­CasinoRating y sigo con una nueva notificación:

UI_RequestIncrement (repo);
Console.WriteLine(" After sync call");

Al ejecutarlo, aparece el siguiente resultado en la consola:

Synchronous Finish
After sync call

Esto se debe a que todo se detuvo mientras se esperaba que cada paso de IncrementCasinoRating finalizara: encontrar el casino, actualizar la calificación y guardar en la base de datos.

Ahora cambiaré el método del repositorio para que emplee los métodos FindAsync y SaveChangesAsync nuevos. Según el patrón asincrónico, también debo convertir en asincrónico el segundo método, al:

  • Agregar la palabra clave async a la firma.
  • Anexar Async al nombre del método.
  • Devolver un objeto del tipo Task; si el método devuelve resultados, entonces debemos devolver Task<tipo_del_resultado>.

Dentro del método, llamo los métodos asincrónicos nuevos FindAsync y SaveChangesAsync debidamente, con la palabra clave await:

public async Task IncrementCasinoRatingAsync(int id)
{
  using (var context = new CasinoSlotsModel())
  {
    var casino=await context.Casinos.FindAsync(id);
    // Rest is delayed until await has received results
    UpdateRating(casino);
    await context.SaveChangesAsync();
    // Method completion is delayed until await has received results
  }
}

Como el método se marcó como asincrónico, en cuanto se alcanza el primer await, el método devuelve el control al proceso autor de la llamada. Pero debo modificar el método que realiza la llamada. Al crear el comportamiento del patrón asincrónico, se genera una trayectoria en cascada, así que el método no solo realiza una llamada al método asincrónico nuevo, sino tiene que ser asincrónico él mismo, ya que otro proceso más debe llamarlo:

private static async void UI_RequestIncrementAsync(
  SimpleRepository repo)
{
  await repo.IncrementCasinoRatingAsync(1);
  // Rest is delayed until await has received results
  Console.WriteLine(" Asynchronous Finish ");
}

Observe que uso await para llamar el método del repositorio. Así, el autor de la llamada sabe que se trata de un método asincrónico. Cuando se alcanza ese await, el control se devuelve al autor de la llamada. También modifiqué el código de inicio:

UI_RequestIncrementAsync(repo);
  Console.WriteLine(" After asynchronous call");

Ahora, al ejecutar este código, el método UI_RequestIncrementAsync devuelve el control al autor de la llamada, ya que también llama al método del repositorio. Esto quiere decir que pasaremos inmediatamente a la siguiente línea del código de inicio, que imprime “After asynchronous call”. Cuando el método del repositorio termina de guardar los datos en la base de datos, devuelve un objeto Task al método que lo llamó, UI_RequestIncrementAsync, el cual, a su vez, ejecuta la parte restante del código e imprime el mensaje en la consola:

After asynchronous call
Asynchronous Finish

De este modo, mi interfaz de usuario pudo finalizar sin tener que esperar que EF termine su parte. Si el método del repositorio hubiera devuelto resultados, estos habrían ascendido hasta el objeto Task en cuanto estuvieran listos.

Este ejercicio pequeño me sirvió para ver los nuevos métodos asincrónicos en acción. Para escribir aplicaciones, así sea del lado cliente o desconectadas, que se valen de un procesamiento asincrónico, resulta enormemente ventajoso que EF6 ahora implemente el nuevo patrón asincrónico en forma tan sencilla.

Convenciones personalizadas

Code First tiene un conjunto de convenciones integradas que determinan el comportamiento predeterminado para la creación de un modelo con las asignaciones para la base de datos desde nuestras clases. Podemos reemplazar estas convenciones con configuraciones explícitas mediante DataAnnotations o la API fluida. En las primeras versiones beta de Code First también se podían definir convenciones propias: por ejemplo una convención que establece que todas las cadenas se asignen a los campos de una base de datos con una longitud máxima de 50 caracteres. Desgraciadamente, el equipo no fue capaz de avanzar esta característica hasta un estado satisfactorio sin retrasar Code First, por lo que no se incorporó en la versión final. Pero ahora volvió en EF6.

Tenemos varias formas para definir nuestras propias convenciones.

La más sencilla es mediante convenciones ligeras. Estas permiten especificar las convenciones en forma armoniosa en el método sobrecargado OnModelCreating de DbContext. Las convenciones ligeras se aplican a nuestras clases y se limitan a la configuración de las propiedades que tienen una correlación directa con la base de datos, como length con MaxLength. Este es un ejemplo de una clase sin ninguna configuración especial y, por lo tanto, los dos campos de cadena se asignarían, de manera predeterminada, al tipo de datos nvarchar(max) en una base de datos SQL Server:

public class Hotel
  {
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
  }

Agregué una convención ligera al modelo para especificar que la API debe revisar las propiedades de todas las entidades que procesa y establecer la propiedad MaxLength de las cadenas en 50:

modelBuilder.Properties<string>()
  .Configure(p => p.HasColumnType("nvarchar"));

En la Figura 1 vemos que Code First se encargó de que la longitud máxima de los campos Name y Description sea 50.

A Custom Convention Made the Max Length of These nvarchars 50
Figura 1 Una convención ligera estableció la longitud máxima de estos campos nvarchar en 50

También podemos definir una convención al implementar una interfaz existente como, por ejemplo, la interfaz de convención para controlar las propiedades DateTime: la clase DateTimePropertyConfiguration en el espacio de nombres System.Data.Entity.ModelConfiguration.Configuration.Properties.Primitive. En el ejemplo de la Figura 2 forcé que las propiedades DateTime se asignen al tipo de fechas de SQL Server en vez del tipo datetime predeterminado. Observe que este ejemplo se atiene a las pautas de Microsoft: no aplicaré mi configuración si el atributo (en este caso ColumnType) ya se configuró.

Figura 2 Asignación de las propiedades DateTime al tipo de fechas de SQL Server

public  class DateTimeColumnTypeConvention :
  IConfigurationConvention<PropertyInfo, DateTimePropertyConfiguration>
  {
    public void Apply(
      PropertyInfo propertyInfo,
      Func<DateTimePropertyConfiguration> configuration)
    {
      // If ColumnType hasn't been configured ...
      if (configuration().ColumnType == null)
      {
        configuration().ColumnType = "date";
      }
    }
  }

Las convenciones tienen un orden de prioridad específico; por lo tanto, debemos asegurarnos de que el tipo de la columna no se haya configurado previamente, antes de aplicar el valor nuevo a ColumnType.

El creador del modelo tiene que saber cómo encontrar esta convención nueva. Así es como se hace, nuevamente en el método sobrecargado OnModelCreating:

modelBuilder.Conventions.Add(new DateTimeColumnTypeConvention());

Existen dos formas más para personalizar las convenciones. Un método permite crear atributos personalizados que podemos usar en las clases con la misma facilidad que los atributos DataAnnotations. El otro es más detallado: en vez de crear una convención que depende de la información que ModelBuilder obtiene de nuestras clases, este método nos permite actuar directamente sobre los metadatos. Ejemplos de los cuatro tipos de convenciones personalizadas se encuentran en la documentación del Centro para desarrolladores de datos de MSDN, “Convenciones de Code First personalizadas” (msdn.microsoft.com/data/jj819164). Cuando EF6 evolucione, este documento incorporará un vínculo a la versión más actual o se modificará para dejarlo alineado con la versión más reciente.

Esquemas múltiples para las migraciones

EF6 permite que las migraciones de Code First funcionen con varios esquemas en las bases de datos. Para obtener más información sobre esta característica, consulte el detallado artículo que escribí en mi blog poco después del lanzamiento de la versión alfa: “Manos a la obra con las migraciones multiempresa en EF6 Alpha” (bit.ly/Rrz1MD). Recuerde, eso sí, que el nombre de la característica cambió de “migraciones multiempresa” a “varios contextos por base de datos”.

Configuraciones en código

Ya podemos especificar ciertas configuraciones pertinentes a la base de datos para Entity Framework en los archivos de configuración de la aplicación (app.config y web.config), lo que nos libra de tener que especificar las configuraciones al inicio de la aplicación o en el constructor del contexto. Ahora, con EF6, podemos crear una clase heredada de una clase DbConfiguration nueva, donde podemos especificar detalles tales como el proveedor de base de datos predeterminado para Code First y la estrategia de inicialización para las bases de datos (por ejemplo DropCreateDatabaseIfModelChanges), entre otros. Podemos crear esta clase DbConfiguration dentro del mismo proyecto que el contexto o en un proyecto independiente, lo que permite que varios contextos puedan usar una misma clase de configuración. La introducción a la configuración en código en msdn.microsoft.com/data/jj680699 entrega ejemplos para las diferentes opciones.

El núcleo de EF ahora está en EF6 y también es código abierto

Mientras que las API de Code First y DbContext siempre estuvieron desconectadas del ciclo de lanzamientos de .NET, el núcleo de EF estaba integrado en .NET Framework. Se trata de todas las funciones centrales: la API de ObjectContext, las consultas, los seguimientos de cambios, el proveedor Entity­Client y mucho más. Es por esta razón que las enumeraciones y los datos espaciales tuvieron que esperar hasta el lanzamiento de .NET Framework 4.5, ya que estos cambios debieron hacerse en las profundidades de las API centrales.

Con EF6, todas las API centrales se incorporaron en el proyecto de código abierto y se implementaron en el paquete de NuGet. Resulta interesante ver los espacios de nombre de EF5 y EF6 lado a lado, tal como se presenta en la Figura 3. Como podemos apreciar, EF6 cuenta con muchos más espacios de nombres.

EF6 Has Acquired the Namespaces of the EF Core APIs from the .NET Framework
Figura 3 EF6 adquirió los espacios de nombres de las API centrales de EF de .NET Framework

Compatibilidad con la enumeración y los datos espaciales para las aplicaciones .NET 4

Tal como mencioné anteriormente, una de las grandes ventajas de tener las API centrales dentro de EF6 es que así se eliminan algunas de las dependencias en las versiones .NET de las características propias de EF, especialmente la compatibilidad con las enumeraciones y los datos espaciales que se agregaron a EF en .NET Framework 4.5. Las aplicaciones existentes para .NET Framework 4 con EF no podían sacar partido de estas características con EF5. Ahora esta limitante desapareció, ya que EF6 incluye la compatibilidad con las enumeraciones y los datos espaciales y, por lo tanto, estas características ya no dependen de .NET Framework. La documentación existente para estas características resulta útil para usarlas en las aplicaciones para .NET Framework 4.

Ayude a avanzar la evolución de EF

Ahora que Entity Framework es un proyecto de código abierto, los desarrolladores pueden ayudarse a sí mismos y a los demás al involucrarse en el diseño y el desarrollo. Puede compartir sus ideas, comentarios y respuestas, ya sea al leer las especificaciones, participar en las conversaciones y los problemas, jugar con el último paquete de NuGet o descargar las versiones compiladas nocturnas y trabajar intensamente en ellas. También puede contribuir código, tanto para uno de los problemas que se indican en el sitio y del que nadie se haya encargado aún, como con alguna idea propia para EF sin la cual no pueda vivir.

Julie Lerman ha recibido el premio al Profesional más valioso (MVP) de Microsoft, es consultora y mentor de .NET, y vive en las colinas de Vermont. Realiza presentaciones sobre acceso a datos y otros temas de Microsoft .NET en grupos de usuarios y congresos en todo el mundo. Mantiene un blog en thedatafarm.com/blog y es la autora de “Programming Entity Framework” (2010), además de una edición para Code First (2011) y una para DbContext (2012), todos de O’Reilly Media. También puede seguir a Julie en Twitter (twitter.com/julielerman).

Gracias a los siguientes expertos técnicos por su ayuda en la revisión de este artículo: Glenn Condron