Enero de 2017

Volumen 32, número 1

Puntos de datos - EF Core 1.1: algunas de mis cosas favoritas

Por Julie Lerman

Julie LermanMientra escribía esta columna (en noviembre de 2016), Entity Framework (EF) Core 1.1 se acababa de lanzar. Entre las versiones 1.0 y 1.1, sucedieron algunos hechos significativos. En concreto, la revisión 1.0.1 corrigió algunos errores críticos que se descubrieron justo mientras se lanzaba la versión 1.0. Para obtener una lista detallada de esas correcciones, puede leer las notas de la versión en bit.ly/2fl5xNE. Aunque la mayoría de estas correcciones están relacionadas con la manera en que las consultas LINQ se transforman a SQL, existen otras para el generador de modelos, la persistencia a través de SaveChanges y las migraciones.

EF Core 1.1 proporciona correcciones de errores adicionales, muchas más mejoras en la interpretación de consultas LINQ y un aumento considerable del rendimiento. No obstante, también se incluyen nuevas características (que ya existían en EF6, pero no en EF Core 1.0, lo que impedía a muchos desarrolladores incluso consultar EF Core) y otras que son completamente nuevas. En este artículo quiero presentar una lista rápida de estos cambios y luego destacar algunos que tienen un gran interés para mí. También proporcionaré vínculos a muchos recursos de valor que uso para seguir el ritmo de EF Core a medida que evoluciona. Para empezar, mucha de esta información se trata en la documentación (docs.microsoft.com/ef) y en las publicaciones en el blog del equipo (bit.ly/2ehZBUB).

En concreto, no se pierda el blog de Arthur Vickers. Arthur es un desarrollador sénior que forma parte del equipo desde el origen del soporte técnico de POCO en EF y ha sido fundamental en la modernización de EF. Tiene una excelente serie de publicaciones sobre EF Core 1.1 en blog.oneunicorn.com.

Microsoft tiene previsto integrar muchas características de EF6 en EF Core, pero no todas. Existen varias características nuevas previstas para las próximas versiones. En este momento, EF Core 1.1 se centra en las actualizaciones que mejorarían el grado de satisfacción de EF Core para un mayor número de desarrolladores. Las siguientes son las más importantes de estas características.

Características de EF6 ahora disponibles en EF 1.1

El método DbSet.Find, que empezó a estar disponible con la introducción de la API de DbContext de EF 4.1, no se incluyó en la lista de destacados en la primera iteración de EF Core, algo que no gustó a muchos desarrolladores. Buscar es un método muy práctico para consultar de manera eficiente una entidad en función de su valor clave. En primer lugar, comprueba si la entidad ya está en la memoria y si EF realiza su seguimiento. Si la entidad no se encuentra en la caché de DbContext, EF ejecuta una consulta FirstOrDefault en la base de datos para recuperar la entidad. Solía ser una consulta SingleOrDefault en EF6 y versiones anteriores. El diseño de Buscar para evitar visitas innecesarias a la base de datos también es una mejora de rendimiento. Si es como yo y quiere ver las tuberías, puede buscar el código en la clase EntityFinder en GitHub (bit.ly/2e9V0Uu).

La otra gran diferencia es que Buscar se encuentra ahora en un servicio EntityFinder; no es solo un método que se implementa directamente en una clase DbSet interna como sucedía en EF6. EF Core está formado por cientos de servicios para encapsular tareas concretas. En sus demos del vídeo de Channel 9 sobre EF Core 1.1 (bit.ly/2fHOCsN), Rowan Miller muestra cómo usar los servicios directamente, así como la manera de reemplazarlos.

También entra a formar parte de EF Core con la actualización 1.1 la característica de EF6 conocida como resistencia de la conexión, que proporciona soporte para administrar fácilmente los problemas de conexión transitorios que pueden surgir al trabajar con una base de datos remota, como Azure SQL Database. El método de extensión EnableRetryOnFailure del proveedor de SQL Database puede establecerse en DbContext.OnConfiguring o la llamada AddDbcontext de Startup.cs si usa la inserción de dependencias de ASP.NET Core. EnableRetryOnFailure llama a SqlServer­RetryingExecutionStrategy, que se hereda del tipo ExecutionStrategy. Puede crear y establecer su propio método ExecutionStrategy, y otros proveedores también pueden predefinir sus propias configuraciones de ExecutionStrategy. Si no está familiarizado con esta característica, es similar a DbExecutionStrategy de EF6, que traté detalladamente en mi curso de Pluralsight, "EF6 Ninja Edition" (bit.ly/PS_EF6).

EF siempre ha proporcionado tres opciones para cargar los datos relacionados. Una, la carga diligente, la habilita el método DbSet.Include para recuperar gráficos de datos en una sola consulta. Las otras dos, en cambio, cargan los datos relacionados cuando los objetos principales ya están en la memoria y mientras la clase DbContext que realiza su seguimiento sigue estando dentro del ámbito. La carga diferida incorpora datos relacionados automáticamente a petición, mientras que con la carga explícita indica explícitamente a EF que cargue los datos relacionados. Include fue la primera de estas que se implementó en EF Core y empezó a estar disponible con EF Core 1.0. Incluso presenta algunas mejoras excelentes comparado con EF6 y versiones anteriores. La carga explícita con el método Load se ha agregado a EF Core 1.1. La carga diferida todavía no se admite, pero se admitirá en algún momento.

Las API de la herramienta de seguimiento de cambios de DbContext permiten acceder directamente a la información de la herramienta de seguimiento de cambios; por ejemplo, permiten obtener o establecer el estado de una entidad con el método DbContext.Entry(someObject).State. EF6 incorporó control adicional con nuevos métodos, como GetDatabase­Values, CurrentValues y OriginalValues. Estos métodos están ahora disponibles en EF Core 1.1.

Nuevas funcionalidades de EF 1.1

EF Core presenta nuevas características nunca vistas en versiones anteriores. Esta es una breve lista de ejemplos: procesamiento por lotes durante la ejecución de SaveChanges, claves externas únicas, el magnífico proveedor InMemory para pruebas, procesamiento de consultas LINQ más inteligente, y asignaciones fluidas más simples e inteligentes.

EF 1.1 incluye algunas nuevas características adicionales. Existe una en especial a la que, como fan indiscutible de Domain-Driven Design (DDD), tengo bastante cariño, que es la compatibilidad con las colecciones encapsuladas.

Campos asignados y colecciones encapsuladas EF Code First solo admitía la asignación a propiedades con un captador y un establecedor, aunque el establecedor fuese privado. Para las navegaciones de colecciones, la propiedad debía ser de tipo ICollection. Si quiere restringir la manera en que se rellenan los valores de las propiedades, la capacidad de encapsular la propiedad es crucial; de ese modo, puede exigir a cualquier persona que use su clase que emplee un método público para garantizar que se respeten las reglas empresariales en torno a esa propiedad. EF6 y las versiones anteriores permiten convertir el establecedor en privado para encapsular las propiedades escalares. No obstante, no existe ningún modo de encapsular realmente las colecciones e impedir que cualquier persona las modifique directamente.

Con EF 1.1, consigue la capacidad de asignación directa a campos, así como de asignación a propiedades IEnumerable. La nueva capacidad de asignación a campos, no solo a propiedades, le permite usar un enfoque más directo que ocultar el establecedor y también admite otras opciones para encapsular valores escalares. Aquí existe una propiedad, DueDate, que tiene un captador pero ningún establecedor, junto con el campo, _dueDate, que está vinculado a la propiedad:

private DateTime _dueDate;
public DateTime DueDate {
  get { return _dueDate;
  }
}

La única manera de establecer la propiedad DueDate es llamar al método CalculateDueDate, que modifica el campo:

private void CalculateDueDate() {
  _dueDate=Start.AddDays(DaysLoaned);
}

EF Core 1.1 necesita una asignación explícita en la clase DbContext para informar a EF de que puede usar el campo _dueDate para la asignación a la base de datos, como, por ejemplo, al devolver los resultados de una consulta. Debe usar los métodos de Property API (y, de manera opcional, HasField) para especificar que el campo _dueDate sustituye a la propiedad DueDate. En este caso, dado que el nombre del campo, _dueDate, sigue una convención de EF, no tengo que usar el método HasField, aunque lo he agregado para que pueda verlo:

protected override void OnModelCreating(ModelBuilder modelBuilder) {
  modelBuilder.Entity<BookLoan>().Property(bl => bl.DueDate)
  .HasField(“_dueDate”);
}

Aunque podía usar establecedores privados antes de que la asignación de campos estuviese disponible, no había ningún modo de encapsular colecciones para impedir que cualquiera pudiese manipular directamente una colección a través de sus métodos Add o Remove. No es cuestión de ocultar el establecedor, sino de ocultar los métodos de colección. Un enfoque común en DDD es convertir la propiedad en IEnumerable. Con EF6 y versiones anteriores, solo se puede realizar la asignación a un tipo de ICollection; no obstante, IEnumerable no es ICollection.

Primero, observé si podía usar la funcionalidad de asignación de campos, pero no es posible con EF 1.1, ya que solo admite la asignación a propiedades escalares. Está previsto que esto cambie en la próxima versión de EF Core, que permitirá la asignación a propiedades de navegación. Cuando señalé esto un día en Twitter, Arthur Vickers me hizo saber que, de hecho, puede realizar la asignación a IEnumerable, lo que de algún modo yo había omitido acerca de EF Core. Por tanto, ahora puedo encapsular y proteger una colección completamente, y exigir a los usuarios de mi API que usen un método para modificar la colección. Este es un ejemplo en que puedo agregar nuevas instancias BookLoan a un libro cada vez que se preste, con la garantía de que se incluye el período del préstamo:

private List<BookLoan> _bookLoans;
public IEnumerable<BookLoan> BookLoans {
  get { return _bookLoans; }
}
public void BookWasLoaned(int daysLoaned){
  _bookLoans.Add(new BookLoan(DateTime.Today, 14));
}

Este es un patrón para conseguir la encapsulación mediante la asignación de IEnumerable. Le recomiendo que lea la publicación de Arthur en el blog (bit.ly/2fVzIfN) para obtener más información, incluido cómo hacerlo sin un campo de respaldo (como mi campo _bookLoans), planes de mejoras y algunas trampas con las que tener cuidado.

Compatibilidad con características específicas de la base de datos EF Core está diseñado para permitir a los proveedores admitir más fácilmente características específicas del almacén de datos. Un ejemplo es que el proveedor de SQL Server para EF Core admite tablas de SQL Server con optimización para memoria para aplicaciones donde tiene un rendimiento increíblemente alto. Para especificar que una entidad se asigne a su tipo de tabla especial, el proveedor de SQL Server tiene un método de extensión que puede usar cuando configure su modelo con la API fluida:

modelBuilder.Entity<Book>().ForSqlServerIsMemoryOptimized();

Esto no solo afectará a la manera en que el código genere inicialmente el script de creación de la tabla, sino que también influirá en cómo EF genere los comandos para insertar datos en la base de datos. Puede ver una interesante demostración sobre esto en el vídeo de Channel 9 mencionado anteriormente, donde Rowan Miller muestra las características de EF 1.1.

Shay Rojansky, quien compiló el proveedor de PostgreSQL para EF Core, escribió un artículo sobre cómo EF Core le permitía admitir características especiales de PosgreSQL, como los tipos de matriz. Puede leerlo en bit.ly/2focZKQ.

Acceso más fácil a los servicios

Como puse de relieve anteriormente con el servicio EntityFinder, EF Core está formado por cientos de servicios. En el vídeo de Channel 9, Rowan muestra cómo acceder a estos servicios directamente en el código y cómo usarlos. Además, puede reemplazar un servicio por su propia personalización. EF 1.1 facilita este proceso con un método de reemplazo simple que se puede usar en OnConfiguring. Puede reemplazar un servicio por otro que se herede de este o que implemente la misma interfaz. No existe ninguna lista de servicios concreta, solo existen clases en las distintas API de EntityFrameworkCore. Un ejemplo accesible consiste en crear una clase que se herede de un asignador de tipos de base de datos, como SqlLiteTypeMapper del proveedor Sqlite, y agregar una nueva regla de asignación de tipos. Asegúrese de que la regla sea de un tipo que la base de datos pueda convertir. (Algunas conversiones de tipos más inteligentes se incluirán en una versión futura de EF Core). A continuación, en OnConfiguring, configure la regla de reemplazo:

optionsBuilder.ReplaceService<SqliteTypeMapper,MySqliteTypeMapper>();

¿Por qué no reemplacé el servicio EntityFinder? Primero, porque me gusta como funciona. En segundo lugar, porque es una clase genérica, que dificulta la creación de una nueva versión, y opté por dejar a un lado esa tarea de momento. Aunque ReplaceService se creó para facilitar el reemplazo de servicios internos, debe tener en cuenta que los datos internos de EF Core podrían cambiar y, por tanto, el reemplazo podría ser problemático más adelante. Se pueden identificar bastante fácilmente porque están en los espacios de nombres que terminan con la palabra Internal. En Microsoft: "Solemos evitar cambios de última hora en los espacios de nombres que no son .Internal en versiones secundarias y revisiones".

Vale la pena destacar (porque causó un poco de revuelo entonces) una corrección para un problema de rendimiento relacionado con las consultas asincrónicas que usaban el método Include, que se descubrió justo cuando la versión 1.0 estaba a punto de lanzarse. Se abordó rápidamente (consulte bit.ly/2faItD1 si está interesado en conocer los detalles) con un aumento del rendimiento del 70 % declarado y también forma parte de la versión 1.1.

Resumen

EF Core tiene muchas características sofisticadas. Es de suma importancia tener en cuenta qué contiene, qué no contiene, qué vendrá y qué nunca se incluirá en EF Core. La documentación de EF Core sobre su relación con EF6 (bit.ly/2fxbVVj) también es un recurso útil. Lo importante es que usted decide cuándo EF Core es conveniente para usted y para sus aplicaciones.

Para la mayor parte del trabajo que realizo, EF Core 1.1 tiene las API y las características que necesito. Como profesional de DDD, la capacidad de encapsular colecciones es para mí la mejor, aunque sigo esperando que se incluyan las asignaciones de tipos complejos para poder usar también los objetos de valor en mis modelos. Esta característica está seleccionada para la próxima versión de EF Core. También me emocionó descubrir recientemente que EF Core había cumplido uno de mis deseos: definir una clase DbContext de solo lectura (no de seguimiento). Se me había pasado por completo que ya se había agregado al principio y que formaba parte de la versión 1.0 de EF Core. Puede leer más información al respecto en mi publicación en el blog en bit.ly/2f75l8m.


Julie Lerman es una Microsoft MVP, mentora y consultora de .NET que vive en las colinas de Vermont. Puede encontrarla haciendo presentaciones sobre el acceso a datos y otros temas de .NET a grupos de usuarios y en conferencias en todo el mundo. Su blog es thedatafarm.com/blog y es la autora de "Programming Entity Framework", así como de una edición de Code First y una edición de DbContext, de O’Reilly Media. Sígala en Twitter en @julielerman y vea sus cursos de Pluralsight en juliel.me/PS-Videos.

Gracias al siguiente experto técnico de Microsoft por revisar este artículo: Rowan Miller


Discuta sobre este artículo en el foro de MSDN Magazine