Entity Framework Queryable async

Estoy trabajando en algunas cosas de API web utilizando Entity Framework 6 y uno de mis métodos de controlador es un “Get All” que espera recibir el contenido de una tabla de mi base de datos como IQueryable . En mi repository, me pregunto si hay alguna razón ventajosa para hacerlo de forma asíncrona ya que soy nuevo en el uso de EF con asincrónico.

Básicamente se reduce a

  public async Task<IQueryable> GetAllUrlsAsync() { var urls = await context.Urls.ToListAsync(); return urls.AsQueryable(); } 

vs

  public IQueryable GetAllUrls() { return context.Urls.AsQueryable(); } 

¿La versión asíncrona realmente rendirá beneficios de rendimiento aquí o estoy incurriendo en una sobrecarga innecesaria al proyectar a una lista primero (usando async mind you) y LUEGO ir a IQueryable?

El problema parece ser que ha entendido mal cómo asíncrona / aguarde trabajar con Entity Framework.

Acerca de Entity Framework

Entonces, veamos este código:

 public IQueryable GetAllUrls() { return context.Urls.AsQueryable(); } 

y ejemplo de su uso:

 repo.GetAllUrls().Where(u => ).Take(10).ToList() 

¿Qué pasa allí?

  1. Obtenemos un objeto IQueryable (sin acceder a la base de datos todavía) usando repo.GetAllUrls()
  2. Creamos un nuevo objeto .Where(u => con una condición especificada usando .Where(u =>
  3. Creamos un nuevo objeto IQueryable con un límite de paginación específico usando .Take(10)
  4. Recuperamos resultados de la base de datos usando .ToList() . Nuestro objeto IQueryable se comstack en sql (como select top 10 * from Urls where ). Y la base de datos puede usar índices, el servidor sql le envía solo 10 objetos desde su base de datos (no todos los mil millones de URL almacenados en la base de datos)

Bien, veamos el primer código:

 public async Task> GetAllUrlsAsync() { var urls = await context.Urls.ToListAsync(); return urls.AsQueryable(); } 

Con el mismo ejemplo de uso tenemos:

  1. Estamos cargando en memoria todos los miles de millones de URL almacenadas en su base de datos usando el await context.Urls.ToListAsync(); .
  2. Tenemos desbordamiento de memoria. Manera correcta de matar tu servidor

Acerca de async / await

¿Por qué se prefiere utilizar async / await? Veamos este código:

 var stuff1 = repo.GetStuff1ForUser(userId); var stuff2 = repo.GetStuff2ForUser(userId); return View(new Model(stuff1, stuff2)); 

¿Qué pasa aquí?

  1. Comenzando en la línea 1 var stuff1 = ...
  2. Enviamos una solicitud al servidor sql que queremos obtener algunas cosas1 para userId
  3. Esperamos (el hilo actual está bloqueado)
  4. Esperamos (el hilo actual está bloqueado)
  5. Servidor Sql enviarnos respuesta
  6. Pasamos a la línea 2 var stuff2 = ...
  7. Enviamos una solicitud al servidor sql que queremos obtener algunas userId para userId
  8. Esperamos (el hilo actual está bloqueado)
  9. Y otra vez
  10. Servidor Sql enviarnos respuesta
  11. Prestamos vista

Así que echemos un vistazo a una versión asíncrona de esto:

 var stuff1Task = repo.GetStuff1ForUserAsync(userId); var stuff2Task = repo.GetStuff2ForUserAsync(userId); await Task.WhenAll(stuff1Task, stuff2Task); return View(new Model(stuff1Task.Result, stuff2Task.Result)); 

¿Qué pasa aquí?

  1. Enviamos solicitud al servidor sql para obtener cosas1 (línea 1)
  2. Enviamos solicitud al servidor SQL para obtener cosas2 (línea 2)
  3. Esperamos las respuestas del servidor sql, pero el hilo actual no está bloqueado, él puede manejar las consultas de otros usuarios
  4. Prestamos vista

La forma correcta de hacerlo

Así que buen código aquí:

 using System.Data.Entity; public IQueryable GetAllUrls() { return context.Urls.AsQueryable(); } public async Task> GetAllUrlsByUser(int userId) { return await GetAllUrls().Where(u => u.User.Id == userId).ToListAsync(); } 

Tenga en cuenta que debe agregar using System.Data.Entity para usar el método ToListAsync() para IQueryable.

Tenga en cuenta que si no necesita filtrado y paginación, no necesita trabajar con IQueryable . Simplemente puede usar await context.Urls.ToListAsync() y trabajar con List materializado List .

Hay una gran diferencia en el ejemplo que ha publicado, la primera versión:

 var urls = await context.Urls.ToListAsync(); 

Esto es malo , básicamente select * from table , devuelve todos los resultados en la memoria y luego aplica el valor where en la colección de memoria en lugar de hacer select * from table where... en la base de datos.

El segundo método no llegará a la base de datos hasta que se aplique una consulta a IQueryable (probablemente a través de una operación de .Where().Select() linq .Where().Select() que solo devolverá los valores db que coinciden con la consulta.

Si sus ejemplos son comparables, la versión async generalmente será un poco más lenta por solicitud ya que hay más sobrecarga en la máquina de estado que el comstackdor genera para permitir la funcionalidad async .

Sin embargo, la principal diferencia (y beneficio) es que la versión async permite más solicitudes simultáneas ya que no bloquea el hilo de procesamiento mientras espera que IO lo complete (consulta de db, acceso a archivos, solicitud web, etc.).