c# - transaction - Entity Framework Queryable async
iqueryable async (2)
El problema parece ser que ha entendido mal cómo funciona async / wait con Entity Framework.
Sobre Entity Framework
Entonces, veamos este código:
public IQueryable<URL> GetAllUrls()
{
return context.Urls.AsQueryable();
}
y ejemplo de uso:
repo.GetAllUrls().Where(u => <condition>).Take(10).ToList()
Que pasa alli
-
Estamos obteniendo el objeto
repo.GetAllUrls()
todavía norepo.GetAllUrls()
a la base de datos) usandorepo.GetAllUrls()
-
Creamos un nuevo objeto
.Where(u => <condition>
con condición especificada usando.Where(u => <condition>
-
Creamos un nuevo objeto
IQueryable
con límite de paginación especificado usando.Take(10)
-
Recuperamos resultados de la base de datos usando
.ToList()
. Nuestro objetoIQueryable
se compila en sql (comoselect top 10 * from Urls where <condition>
). Y la base de datos puede usar índices, el servidor SQL solo le envía 10 objetos de 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<IQueryable<URL>> GetAllUrlsAsync()
{
var urls = await context.Urls.ToListAsync();
return urls.AsQueryable();
}
Con el mismo ejemplo de uso obtuvimos:
-
Estamos cargando en memoria todos los miles de millones de URL almacenadas en su base de datos usando el
await context.Urls.ToListAsync();
. - Tenemos desbordamiento de memoria. La forma correcta de matar a tu servidor
Sobre async / await
¿Por qué se prefiere usar 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í?
-
Comenzando en la línea 1
var stuff1 = ...
-
Enviamos una solicitud al servidor sql de que queremos obtener algunas cosas1 para
userId
- Esperamos (el hilo actual está bloqueado)
- Esperamos (el hilo actual está bloqueado)
- .....
- Servidor SQL envíenos una respuesta
-
Pasamos a la línea 2
var stuff2 = ...
-
Enviamos una solicitud al servidor sql de que queremos obtener algunas
userId
parauserId
- Esperamos (el hilo actual está bloqueado)
- Y otra vez
- .....
- Servidor SQL envíenos una respuesta
- Representamos la vista
Así que veamos una versión asíncrona:
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í?
- Enviamos una solicitud al servidor SQL para obtener cosas1 (línea 1)
- Enviamos solicitud al servidor SQL para obtener cosas2 (línea 2)
- Esperamos las respuestas del servidor SQL, pero el hilo actual no está bloqueado, puede manejar consultas de otros usuarios
- Representamos la vista
Forma correcta de hacerlo
Tan buen código aquí:
using System.Data.Entity;
public IQueryable<URL> GetAllUrls()
{
return context.Urls.AsQueryable();
}
public async Task<List<URL>> 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, paginación y demás, no necesita trabajar con
IQueryable
.
Puede usar
await context.Urls.ToListAsync()
y trabajar con la
List<Url>
materializada
List<Url>
.
Estoy trabajando en algunas cosas de API web usando Entity Framework 6 y uno de mis métodos de controlador es un "Obtener todo" que espera recibir el contenido de una tabla de mi base de datos como
IQueryable<Entity>
.
En mi repositorio, me pregunto si hay alguna razón ventajosa para hacer esto de forma asincrónica, ya que soy nuevo en el uso de EF con async.
Básicamente se reduce a
public async Task<IQueryable<URL>> GetAllUrlsAsync()
{
var urls = await context.Urls.ToListAsync();
return urls.AsQueryable();
}
vs
public IQueryable<URL> GetAllUrls()
{
return context.Urls.AsQueryable();
}
¿La versión asíncrona realmente generará beneficios de rendimiento aquí o incurriré en gastos indirectos innecesarios al proyectar primero en una Lista (usando asíncrono) y luego ir a IQueryable?
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 a la memoria y luego aplica el
where
contra eso en la colección de memoria en lugar de
select * from table where...
contra 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 de 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 genera el compilador 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 se complete IO (consulta db, acceso a archivos, solicitud web, etc.).