Bienvenidos a todos a un nuevo post en el que hoy veremos la importancia de la interfaz IQueryable. Si has utilizado LINQ en el pasado, lo más posible es que hayas trabajado con ella, incluso si no lo sabías!
Índice
Como siempre, este post está dentro del curso de Entity Framework, y en ciertos casos puede ser importante para entender el contexto.
1 - ¿Qué es la interfaz IQueryable?
Lo primero que tenemos que entender es ¿qué es exactamente la interfaz IQueryable? Básicamente es una interfaz que hereda de IEnumerable
y permite ir “montando” la consulta poco a poco hasta que la convertimos en SQL.
2 - ¿Por qué es importante la interfaz IQueryable?
como he explicado en el punto anterior, nos permite ir montando la consulta que se va a ejecutar poco a poco, esto es muy muy importante en ciertos escenarios. Ya que nos permite ir montando dicha consulta en función a diferentes filtros o diferentes pasos en nuestra aplicación.
La forma más fácil de verlo es cuando tenemos paginación en nuestra API.
Para realizar este post, he modificado el seed para incluir 50 usuarios:
public class UserSeed : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.HasQueryFilter(a => !a.IsDeleted);
builder.HasData(
BuildUsers()
);
}
private List<User> BuildUsers()
{
List<User> users = new List<User>();
foreach (int index in Enumerable.Range(1, 50))
{
users.Add(new User
{ Email = $"example{index}@mail.com", Id = index, UserName = $"user{index}", IsDeleted = false });
}
return users;
}
}
A partir de aquí, lo que tenemos que entender es que tenemos una base de datos con 50 usuarios, lo cual nos ayudará a entender la interfaz IQueryable.
Volvemos a hablar de la paginación, la forma más sencilla de hacer paginación es a través de tener el número de página y el número de elementos que quieres coger:
var result = _context.Users
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize);
Lo importante aquí es entender que tanto el método .Take()
como .Skip()
nos devuelven un IQueryable
, y esto es importante de entender, porque mientras utilicemos Iqueryable NO vamos a estar consultando la base de datos.
Lo cual nos abre más posibilidades a la hora de filtrar. por ejemplo, ahora podemos mandar otro filtro adicional que es muy común, básicamente en una columna, en nuestro caso la del email, vamos a filtrar por ella.
var result = _context.Users
.Where(a=>a.Email.Contains(emailFilter))
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize);
Como puedes ver nuestro where
también funciona con IQueryable
, por lo que por ahora, no estamos consultando la base de datos.
En nuestro caso en particular, llamaremos a la base de datos cuando se ejecute .ToListAsync()
, pero también se ejecutará haciendo First
, Single
, etc.
2.1 - Cuidado con las subconsultas
Cuando utilizamos IQueryable
debemos de pensar a la hora de hacer múltiples operaciones contra la base de datos, por ejemplo en el siguiente escenario que parece todo muy normal:
IQueryable<User> users = _context.Users;
foreach (User user in users)
{
ICollection<Wokringexperience> experiences = user.Wokringexperiences;
}
En verdad lo que tenemos es una situación de consultas N+1, lo cual puede ser muy negativo para tu aplicación y por supuesto para el rendimiento.
Esto es debido a que el foreach inicial hace la llamada, porque users es IQueryable, pero una vez dentro del foreach pues se ejecuta. Y luego dentro de cada iteración estamos llamando a las WorkingExperiences
, las cuales están relacionadas y en otra tabla de la base de datos, por lo que es otra llamada SQL cada vez.