web-dev-qa-db-fra.com

Entity Framework interrogeable async

Je travaille sur certains éléments de l'API Web avec Entity Framework 6 et l'une de mes méthodes de contrôleur est un "Tout obtenir" qui s'attend à recevoir le contenu d'une table de ma base de données sous la forme IQueryable<Entity>. Dans mon référentiel, je me demande s’il existe une raison avantageuse de le faire de manière asynchrone, car je suis nouveau dans l’utilisation de EF avec async.

Fondamentalement, cela revient à

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

contre

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

Est-ce que la version asynchrone apportera réellement des avantages en termes de performances ici ou suis-je sujet à des frais généraux inutiles en projetant d’abord sur une liste (en utilisant async, gardez bien cela) et ALORS sur IQueryable?

79
Jesse Carter

Le problème semble être que vous avez mal compris comment async/attend de travailler avec Entity Framework.

À propos de Entity Framework

Alors, regardons ce code:

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

et exemple d'utilisation:

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

Que se passe-t-il là?

  1. Nous obtenons l'objet IQueryable (n'accédant pas encore à la base de données) en utilisant repo.GetAllUrls()
  2. Nous créons un nouvel objet IQueryable avec la condition spécifiée en utilisant .Where(u => <condition>
  3. Nous créons un nouvel objet IQueryable avec une limite de pagination spécifiée à l'aide de .Take(10).
  4. Nous récupérons les résultats de la base de données en utilisant .ToList(). Notre objet IQueryable est compilé en SQL (comme select top 10 * from Urls where <condition>). Et la base de données peut utiliser des index, SQL Server ne vous envoie que 10 objets (pas tous les milliards d’URL stockés dans la base de données)

Ok, regardons le premier code:

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

Avec le même exemple d'utilisation, nous avons:

  1. Nous chargeons en mémoire tous les milliards d’URL stockés dans votre base de données en utilisant await context.Urls.ToListAsync();.
  2. Nous avons dépassé la mémoire. Bonne façon de tuer votre serveur

À propos de l'async/wait

Pourquoi async/wait est-il préférable d'utiliser? Regardons ce code:

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

Que se passe t-il ici?

  1. À partir de la ligne 1 var stuff1 = ...
  2. Nous envoyons une demande au serveur SQL que nous voulons obtenir des choses1 pour userId
  3. Nous attendons (le fil actuel est bloqué)
  4. Nous attendons (le fil actuel est bloqué)
  5. .....
  6. Sql Server nous envoie la réponse
  7. Passons à la ligne 2 var stuff2 = ...
  8. Nous envoyons une demande au serveur SQL que nous voulons obtenir des choses2 pour userId
  9. Nous attendons (le fil actuel est bloqué)
  10. Et encore
  11. .....
  12. Sql Server nous envoie la réponse
  13. Nous rendons la vue

Voyons donc une version asynchrone de celle-ci:

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

Que se passe t-il ici?

  1. Nous envoyons une requête au serveur SQL pour obtenir stuff1 (ligne 1)
  2. Nous envoyons une requête au serveur SQL pour obtenir stuff2 (ligne 2)
  3. Nous attendons les réponses du serveur SQL, mais le thread actuel n'est pas bloqué, il peut gérer les requêtes d'autres utilisateurs.
  4. Nous rendons la vue

Bonne façon de le faire

Donc bon code ici:

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();
}

Notez que vous devez ajouter using System.Data.Entity pour utiliser la méthode ToListAsync() pour IQueryable.

Notez que si vous n'avez pas besoin de filtrage et de pagination, vous n'avez pas besoin de travailler avec IQueryable. Vous pouvez simplement utiliser await context.Urls.ToListAsync() et travailler avec List<Url> matérialisé.

194
Viktor Lova

Il y a une différence énorme dans l'exemple que vous avez posté, la première version:

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

Ceci est mauvais, il fait essentiellement select * from table, renvoie tous les résultats en mémoire et applique ensuite le where à celui de la collection de mémoire plutôt que de faire select * from table where... contre la base de données .

La deuxième méthode ne touchera pas la base de données tant qu'une requête n'aura pas été appliquée à l'opération IQueryable (probablement via une opération de style linq .Where().Select() qui ne renverra que les valeurs de base de données correspondant à la requête.

Si vos exemples étaient comparables, la version async sera généralement un peu plus lente à la demande car il y a plus de temps système dans la machine à états générée par le compilateur pour permettre la fonctionnalité async.

Cependant, la principale différence (et l'avantage) réside dans le fait que la version async autorise davantage de demandes simultanées, car elle ne bloque pas le processus de traitement en attendant que IO soit terminé (requête de la base de données, accès au fichier). , demande Web, etc.).

9
Trevor Pilley