Julio de 2018

Volumen 33, número 7

Puntos de datos: tipos de consulta EF Core 2.1

Por Julie Lerman

Julie Lerman¡EF Core 2.1 ya está aquí! Y hay muchas características nuevas y mejoras. En lugar de asumir la revista entera para informarle sobre todos ellos, que compartiré con usted la nueva característica de tipo de consulta, que permite más fácilmente sin necesidad de true entidades con propiedades de clave para consumir los resultados de consulta la base de datos.

Antes de tipos de consultas, era posible escribir consultas en vistas de base de datos y ejecutar procedimientos almacenados con EF Core, pero había limitaciones. Para las vistas, había que depender del hecho de que EF Core no conoce la diferencia entre una vista y una tabla en la base de datos. Puede crear entidades que formaban parte de su modelo de DbContext, crear DbSets para esas entidades y, a continuación, escribir consultas en esas DbSets. Pero una gran cantidad de advertencias que se suministra con ese flujo de trabajo, como tener que tenga cuidado de no para editar los objetos resultantes y causando accidentalmente SaveChanges se intenta ejecutar un comando de actualización, lo que produciría un error en la base de datos a menos que la vista sea actualizable. Al ejecutar los procedimientos almacenados mediante el método FromSql, era necesario volver a enlazar los resultados a una entidad true que formaba parte del modelo de datos, lo que significaba la adición de tipos adicionales al modelo de datos que realmente no necesita estar ahí.

El nuevo tipo de consulta permite rutas de acceso más sencillos para trabajar con vistas, procedimientos almacenados y otros medios de la consulta de la base de datos. Esto es porque el tipo de consulta le permiten al interactuar con tipos que no tienen propiedades de clave y el mapa de EF Core permite a los objetos de base de datos que no tienen claves principales. EF siempre ha sido dependa de las claves, por lo que se trata de un gran paso para EF Core. Además, el tipo de consultas le ayuda a evitar cualquier interacción con el seguimiento de cambios, para que no tenga que agregar código para proteger la aplicación de en tiempo de ejecución accidental de las excepciones relacionadas con entidades que no son actualizables. Incluso puede usar tipos de consultas para asignar a tablas, le obliga a ser de solo lectura.

En este artículo, voy a explorar tres funcionalidades habilitadas por tipos de consulta:

  • Realizar una consulta en las vistas de base de datos
  • Otra característica nueva denominada "consultas de definición"
  • Capturar los resultados de consultas de FromSql con tipos sin entidad

Vitales para tipos de consulta es dejar que DbContext ModelBuilder saber que se debería reconocer un tipo como un tipo de consulta. Hacerlo mediante la creación de una propiedad DbQuery en el contexto o con el método ModelBuilder.Query. Ambos son nuevas.

Si ha usado EF o EF Core, debe estar familiarizado con DbSet, la clase EF que le permite consultar y actualizar las entidades de un tipo determinado a través de una clase DbContext. DbQuery es primo un DbSet, ajuste los tipos sin entidad y lo que le permite ejecutar consultas de solo lectura en tablas y vistas. Y estos tipos de encapsulado en un DbQuery son tipos de consulta.

La convención de EF Core para un DbQuery es similar a una clase DbSet en que EF Core espera el nombre de la propiedad DbQuery para que coincida con el nombre del objeto de base de datos al que se asigna.

Son dos puntos que debe tener en cuenta que las migraciones no pueden crear vistas para según las asignaciones y EF Core no se puede aplicar ingeniería inversa a las vistas (todavía).

La asignación a y consultar una vista de base de datos

Usará DbQuery para el primer ejemplo, la asignación a una vista de base de datos y consultas desde él. Este DbQuery se da por supuesto que hay una clase ya definida, así como una vista denominada AuthorArticleCounts en la base de datos:

public DbQuery<AuthorArticleCount>
  AuthorArticleCounts{get;set;}

Esto por sí solo, podrá consultar una vista de base de datos. Vamos a realizar copias de seguridad, sin embargo, para ver el modelo se muestra en figura 1.

Figura 1 las clases de entidad para el modelo de ejemplo

public class Magazine
{
  public int MagazineId { get; set; }
  public string Name { get; set; }
  public string Publisher { get; set; }
  public List<Article> Articles { get; set; }
}
public class Article
{
  public int ArticleId { get; set; }
  public string Title { get; set; }
  public int MagazineId { get; set; }
  public DateTime PublishDate { get;  set; }
  public Author Author { get; set; }
  public int AuthorId { get; set; }
}
public class Author
{
  public int AuthorId { get; set; }
  public string Name { get; set; }
  public List<Article> Articles { get; set; }
}

Estoy usando un modelo simple con tres entidades para administrar las publicaciones: Magazine, artículo y autor.

En mi base de datos, además de las tablas de autores, artículos y revistas, tengo una vista denominada AuthorArticleCounts, definida para devolver el nombre y el número de artículos que se ha escrito un autor:

SELECT
  a.AuthorName,
  Count(r.ArticleId) as ArticleCount
from Authors a
  JOIN Articles r on r.AuthorId = a.AuthorId
GROUP BY a.AuthorName

También he creado la clase AuthorArticleCount que coincida con el esquema de los resultados de la vista. En la clase, realizaron los establecedores de propiedad privada para dejar claro que esta clase es de solo lectura, aunque EF Core no intentar nunca hacer un seguimiento o conservar los datos de un tipo de consulta.

public class AuthorArticleCount
{
  public string AuthorName { get; private set; }
  public int ArticleCount { get; private set; }
}

Con la vista de base de datos en su lugar y una clase diseñada para utilizar los resultados, todo lo que necesito para asignarlas juntos es una propiedad de DbQuery en mi clase DbContext, el mismo ejemplo que mostré anteriormente:

public DbQuery<AuthorArticleCount> AuthorArticleCounts{get;set;}

Ahora EF Core estará satisfecho trabajar con la clase AuthorArticleCount, incluso si no tiene ninguna propiedad clave, ya que EF Core comprende esto sea un tipo de consulta. Puede usarlo para escribir y ejecutar consultas en la vista de base de datos.

Por ejemplo, esta consulta sencilla de LINQ:

var results=_context.AuthorArticleCounts.ToList();

hará que el siguiente código SQL para enviarse a mi base de datos de SQLite:

SELECT "v"."ArticleCount", "v"."AuthorName"
  FROM "AuthorArticleCounts" AS "v"

Los resultados son un conjunto de objetos AuthorArticleCount, como se muestra en figura 2.

Resultados de consulta de uno a uno
Figura 2 resultados de consulta de uno a uno

Y es totalmente consciente de estos objetos ChangeTracker de contexto usado para ejecutar la consulta.

Se trata de una experiencia más agradable que implementaciones anteriores de EF Core y Entity Framework donde se tratan las vistas de base de datos como tablas, sus resultados se tenían que ser entidades y tenía que tenga cuidado de no accidentalmente un seguimiento con el seguimiento de cambios.

Es posible ejecutar consultas sin predefinir una DbQuery en la clase DbContext. DbSet permite esto, también, con el método Set de una instancia de DbContext. Para un DbQuery, puede escribir una consulta como:

var results=_context.Query<AuthorArticleCount>().ToList();

Configuración de asignaciones de tipo de consulta

Este DbQuery fácilmente funcionaba porque todo lo que sigue la convención. Cuando DbSets y sus entidades no siguen las convenciones de EF Core, use la API Fluent o las anotaciones de datos para especificar las asignaciones correctas en el método OnModelCreating. Y comience por identificar qué entidad en el modelo que desea aplicar mediante el método de entidad del ModelBuilder. Tal como DbSet ganado sobre un elemento cousin en DbQuery, el método de entidad también tiene un elemento cousin nueva: Consulta. Este es un ejemplo del uso del método de consulta para que señale la AuthorArticleCounts DbQuery a una vista de un nombre diferente, mediante el nuevo método de opción (similar al método ToTable):

modelBuilder.Query<AuthorArticleCount>().ToView(
  "View_AuthorArticleCounts");

El método de consulta < T > devuelve un objeto QueryTypeBuilder. Opción es un método de extensión. Hay una serie de métodos que puede usar al refinar el tipo de consulta. QueryTypeBuilder tiene un subconjunto de los métodos EntityTypeBuilder: HasAnnotation, HasBaseType, HasOne, HasQueryFilter, IgnoreProperty y UsePropertyAccessMode. Es una buena explicación sobre la opción y ToTable resaltado como una sugerencia en la documentación de tipos de consultas que le recomiendo (bit.ly/2kmQhV8).

Tipos de consultas en relaciones

Tenga en cuenta el método de HasOne. Es posible que un tipo de consulta sea dependiente (también conocido como "secundario") en una relación uno a uno o uno a varios con una entidad, aunque no con otro tipo de consulta. Tenga en cuenta también que los tipos de consulta no son casi tan flexibles como las entidades en relaciones, que es razonable en mi opinión. Y tendrá que configurar las relaciones de una manera determinada.

Comenzaré con una relación uno a uno entre la entidad de autor y AuthorArticleCount. Las reglas más importantes de esta implementación son:

  • El tipo de consulta debe tener una propiedad de navegación hacia el otro extremo de la relación.
  • La entidad no puede tener una propiedad de navegación para el tipo de consulta.

En el último caso, si fuera a agregar una propiedad AuthorArticleCount al autor, el contexto pensaría la AuthorArticleCount es una entidad y se produciría un error con el generador de modelos.

He mejorado el modelo con dos cambios:

En primer lugar, he modificado el AuthorArticleCount para incluir una propiedad del autor:

public Author Author { get; private set; }

A continuación, agregué una asignación unívoca entre el autor y AuthorArticleCount:

modelBuilder.Query<AuthorArticleCount>()
            .HasOne<Author>()
            .WithOne();

Ahora puedo ejecutar las consultas LINQ para carga diligente de la propiedad de navegación de autor, por ejemplo:

var results =_context.AuthorArticleCounts.Include("Author").ToList();

Los resultados se muestran en figura 3.

Resultados de una relación uno a uno entre un tipo de consulta y una entidad de carga diligente
Figura 3 resultados de una relación uno a uno entre un tipo de consulta y una entidad de carga diligente

Tipos de consulta en una relación uno a varios

Una relación uno a varios también requiere que el tipo de consulta sea el extremo dependiente, nunca la entidad de seguridad (también conocido como elemento primario). Para explorar este, he creado una nueva vista a través de la tabla de artículos en la base de datos denominada ArticleView:

CREATE VIEW ArticleView as select Title, PublishDate, MagazineId from Articles;

Y he creado una clase ArticleView:

public class ArticleView
{
  public string Title { get; set; }
  public Magazine Magazine { get; set; }
  public int MagazineId { get; set; }
  public DateTime PublishDate { get; set; }
}

Por último, especifiqué que ArticleView es un tipo de consulta y define su relación con la entidad de la revista, donde una revista puede tener muchos ArticleViews:

modelBuilder.Query<ArticleView>().HasOne(a => a.Magazine).WithMany();

Ahora puedo ejecutar una consulta que recupera los gráficos de datos. Un método Include, usaré nuevamente. Recuerde que no hay ninguna referencia al tipo de consulta en la clase Magazine, por lo que no se puede consultar un gráfico de una revista con su ArticleViews y ver los gráficos. Solo puede navegar desde ArticleView revista, por lo tanto, el tipo de consulta que puede realizar:

var articles=_context.Query<ArticleView>().Include(m=>m.Magazine).ToList();

Tenga en cuenta que no había creado un DbQuery por lo que estoy usando el método de consulta en la consulta.

La documentación de API de HasOne, que encontrará en bit.ly/2Im8UqR, proporciona más detalles sobre el uso de este método.

La nueva característica de consulta de definición

Además de opción, hay un nuevo método en QueryTypeBuilder que nunca existieron en EntityTypeBuilder y eso es ToQuery. ToQuery le permite definir una consulta directamente en la clase DbContext, y este tipo de consulta se conoce como una "consulta de definición". Puede escribir consultas LINQ e incluso usar FromSql al crear consultas de definición. Andrew Peters desde el equipo de EF se explica, "un uso de ToQuery es para las pruebas con el proveedor en memoria. Si mi aplicación está utilizando una vista de base de datos, también puedo definir una ToQuery que se usarán solo si estoy orientado a en memoria. De esta manera que puedo simular la vista de base de datos de prueba."

Para empezar, creé la clase MagazineStatsView para consumir los resultados de la consulta:

public class MagazineStatsView
{
  public MagazineStatsView(string name, int articleCount, int authorCount)
  {
    Name=name;
    ArticleCount=articleCount;
    AuthorCount=authorCount;
  }
  public string Name { get; private set; }
  public int ArticleCount { get; private set; }
  public int AuthorCount{get; private set;}
}

A continuación, crea una consulta de definición en OnModelCreating que consulta las entidades de la revista y compilaciones MagazineStatsView objetos en los resultados de:

modelBuilder.Query<MagazineStatsView>().ToQuery(
      () => Magazines.Select(  m => new MagazineStatsView(
                     m.Name,
                     m.Articles.Count,
                     m.Articles.Select(a => a.AuthorId).Distinct().Count()
                    )
                )
  );

También podría crear un DbQuery para hacer un poco más reconocible mi nueva consulta de definición, pero quería ver que puedo usar esto todavía sin un DbQuery explícita.  Esta es una consulta LINQ para MagazineStatsView. Siempre se controlarán mediante la consulta de definición:

var results=_context.Query<MagazineStatsView>().ToList();

Según los datos que he usado para inicializar la base de datos, los resultados de la consulta, se muestra en figura 4, mostrar correctamente los dos artículos y un autor único para MSDN Magazine y dos artículos con dos autores únicos para el nuevo Yorker.

Resultados de la consulta con una consulta de definición
Figura 4: resultados de la consulta con una consulta de definición

Capturar los resultados de FromSql en tipos sin entidad

En versiones anteriores de Entity Framework, era posible ejecutar SQL sin procesar y capturar los resultados en tipos aleatorios. Se están más cerca que se va a llevar a cabo este tipo de consulta gracias a los tipos de consulta. Con EF Core 2.1, el tipo que desee utilizar para capturar los resultados de consultas SQL sin procesar no tiene que ser una entidad, pero todavía tiene que ser conocido por el modelo como un tipo de consulta.

Hay una excepción a esto, que es que es posible (con muchas limitaciones) devolver tipos anónimos. Incluso este soporte limitado puede resultarle útil, por lo que merece la pena conocer. Esta es una consulta que devuelve un tipo anónimo con FromSql y una consulta SQL sin formato:

context.Authors.FromSql("select authorid,authorname from authors").ToList();

Devolver tipos anónimos consultando las entidades solo funciona cuando la proyección incluye la clave principal del tipo representado por la clase DbSet. Si no se incluye AuthorId, un error en tiempo de ejecución podría informar acerca de AuthorId no está en la proyección. O bien si comencé con el contexto. Magazines.FromSql con la misma consulta que acabo de mostrar, el error en tiempo de ejecución se quejan MagazineId no está disponible.

Un mejor uso de esta característica es predefinir un tipo y asegúrese de que la clase DbContext es consciente de ese tipo, definir un DbQuery o especificar modelBuilder.Query para el tipo en OnModelCreating. A continuación, puede usar FromSql para consultar y capturar los resultados. Como un ejemplo bastante elaborado, o quizás me debo decir incluso más retocado que algunos de los ejemplos que he usado ya, aquí es una nueva clase, publicador, que no es una entidad o una parte de mi PublicationsContext:

public class Publisher
{  
  public string Name { get; private set; }
  public int YearIncorporated { get; private set; }
}

También es una clase de solo lectura, dado que tengo que otra aplicación que cuenten con datos del publicador.

Creé un DbQuery < publicador > denominado publicadores en el contexto, y ahora puedo usarla para ejecutar la consulta SQL sin formato:

var publishers=_context.Publishers
                  .FromSql("select name, yearfounded from publishers")
                  .ToList();

SQL sin formato también puede ser una llamada para ejecutar un procedimiento almacenado. Siempre que el esquema de los resultados coinciden con el tipo (en este caso, el publicador), puede hacer, incluso pasar parámetros.

Colocar el polaco en EF Core

Si ha se ha indeciso frente a usar EF Core hasta que fue para entornos de producción, por último ha llegado la hora. EF Core 2.0 realizado un gran paso en las características y funcionalidad y la versión 2.1 incluye ahora características que colocan un polaco real en el producto. La espera para las características de EF6 que aparezcan en EF Core ha sido debe en parte al hecho de que el equipo de EF ha no acaba de copiar el antiguas implementaciones pero implementaciones inteligentes y más funcionales se encuentra. Tipos de consultas son un buen ejemplo de esto, en comparación con la forma en que las vistas y SQL sin procesar se admitían en versiones anteriores de Entity Framework. No olvide revisar las nuevas características de EF Core 2.1, lea la sección "Características de nuevo en EF Core 2.1" de la documentación de EF Core en bit.ly/2IhyHQR.


Julie Lerman es directora regional de Microsoft, MVP de Microsoft, instructora y consultora del equipo de software. Vive en las colinas de Vermont. Puede encontrarla haciendo presentaciones sobre el acceso a datos y otros temas en 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: Andrew Peters
Andrew Peters es ingeniero jefe en el equipo de Entity Framework. Durante sus años 9 en el equipo, Andrew ha trabajado en, entre otras cosas, LINQ, Code First y migraciones y fue uno de los clientes potenciales de arquitectura para EF Core. En su tiempo libre Andrew disfruta de juegos, guitarra, cocinar y pasar tiempo con su familia jóvenes.


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