web-dev-qa-db-fra.com

C # LINQ. Tout ne fonctionne pas sur DocumentDb CreateDocumentQuery

J'essaie d'interroger Art qui a un produit d'un certain type. Voici mon modèle pour l'Art:

  public string Title { get; set; }
  public string Description { get; set; }
  public List<Product> Products { get; set; }
  public string PaintedLocation { get; set; }

De là, tout ce que je fais est la requête LINQ suivante:

List<Art> items = DocumentDbHelper.Client.CreateDocumentQuery<Art>(collection.DocumentsLink)
                               .Where(i => i.type == "art")
                               .Where(i => i.Products.Any(p => p.Name == productType))
                               .AsEnumerable()
                               .ToList();

J'obtiens l'erreur suivante:

"Method 'Any' is not supported."

Je suis allé à la page que le code référence pour voir ce qui est pris en charge mais je ne le vois pas dire que Any () n'est pas pris en charge, donc je fais probablement quelque chose de incorrect. Toute aide est appréciée.

[~ # ~] mise à jour [~ # ~]

C'est vraiment étrange pour moi, donc je l'ai interrompu pour voir ce qui était retourné des deux résultats pour mieux déboguer le problème à ceci:

        List<Art> items = DocumentDbHelper.Client.CreateDocumentQuery<Art>(collection.DocumentsLink)
                               .Where(i => i.Id.Contains("art"))
                               .AsEnumerable()
                               .ToList();

        items = items.Where(i => i.Products.Any(p => p.Name == productType))
                     .AsEnumerable()
                     .ToList();

Pour une raison quelconque, cela fonctionne, je ne suis pas fan de cela car puisque je le convertis en liste, il exécute la requête deux fois - mais c'est au moins la preuve que Any () et Select () devraient techniquement fonctionner.

25
gregwhitworth

L'une des plus grandes confusions avec les requêtes LINQ contre IQueryable<T> Est qu'elles ressemblent exactement aux requêtes contre IEnumerable<T>. Eh bien, le premier utilise Expression<Func<..>> Chaque fois que le dernier utilise Func<..>, Mais sauf si l'on utilise des déclarations explicites, cela n'est pas si visible et semble sans importance. Cependant, la grande différence survient au moment de l'exécution. Une fois que la requête IEnumerable<T> Est compilée avec succès, elle ne fonctionne qu'à l'exécution, ce qui n'est pas le cas avec IQueryable<T>. Une requête IQuaryable<T> Est en fait une arborescence d'expression qui est traitée au moment de l'exécution par le fournisseur de requête. D'un côté, c'est un gros avantage, de l'autre côté, puisque le fournisseur de requête n'est pas impliqué au moment de la compilation de la requête (toutes les méthodes sont fournies comme méthodes d'extension par la classe Queryable), il n'y a aucun moyen de savoir si le fournisseur prend en charge une construction/méthode ou non jusqu'à l'exécution. Les personnes qui utilisent Linq to Entities le savent très bien. Pour rendre les choses plus difficiles, il n'y a pas de documentation claire sur ce que le fournisseur de requêtes spécifique prend en charge et, plus important encore, ce qu'il ne prend pas en charge (comme vous l'avez remarqué à partir du lien "ce qui est pris en charge" que vous avez fourni).

Quelle est la solution (et pourquoi votre deuxième code fonctionne)

L'astuce consiste à écrire le maximum possible (iesupporté par le fournisseur de requêtes) contre IQueryable<T>, Puis à passer à IEnumerable<T> Et à faire le reste (rappelez-vous, une fois compilé, IEnumerable<T> Fonctionne juste). Le basculement est effectué par AsEnumerable() appel. Et c'est pourquoi votre deuxième code fonctionne - car la méthode Any non prise en charge n'est plus dans le contexte du fournisseur de requêtes DocumentDb. Notez que l'appel ToList n'est pas nécessaire et la requête n'est pas exécutée deux fois - en fait, de cette façon, il n'y a pas une seule requête, mais deux - une dans la base de données et une en mémoire. Donc, quelque chose comme ça serait suffisant

List<Art> items = DocumentDbHelper.Client.CreateDocumentQuery<Art>(collection.DocumentsLink)
                               .Where(i => i.type == "art")
                               .AsEnumerable() // The context switch!
                               .Where(i => i.Products.Any(p => p.Name == productType))
                               .ToList();

Enfin, ce qui est réellement pris en charge par le fournisseur de requêtes DocumentDb

Ce n'est pas tout à fait clair à partir de la documentation, mais la réponse est: exactement (et seulement) ce qui y est inclus. En d'autres termes, les seuls opérateurs de requête pris en charge (ou mieux dire Queryable ou Enumerable méthodes d'extension) sont

  • Sélectionner
  • SelectMany
  • Commandé par
  • OrderByDescending

Comme vous pouvez le voir, c'est très limité. Oubliez les opérateurs de jointure et de regroupement, Any, Contains, Count, First, Last etc. La seule bonne chose est que c'est facile à mémoriser :)

Comment le sais-je? Eh bien, comme d'habitude lorsque quelque chose n'est pas clair dans la documentation, on utilise soit des essais et des erreurs, soit un décompilateur. Apparemment, dans ce cas, le premier n'est pas applicable, j'ai donc utilisé le plus tard. Si vous êtes curieux, utilisez votre décompilateur préféré et vérifiez le code de la classe interne DocumentQueryEvaluator dans le Microsoft.Azure.Documents.Client.dll.

75
Ivan Stoev

J'utilise le dernier ciblage de nuget Azure DocumentDB .Net 4.6.

<package id="Microsoft.Azure.DocumentDB" version="1.5.0" targetFramework="net46" />

Voici l'exemple de code qui fonctionne bien pour moi.

using System.Collections.Generic;
using System.Linq;
using Microsoft.Azure.Documents.Client;
using Microsoft.Azure.Documents.Linq;

var book = client.CreateDocumentQuery<Book>(collectionLink)
                    .Where(b => b.Title == "War and Peace")
                    .Where(b => b.Publishers.Any(p => p.IsNormalized()))
                    .AsEnumerable().FirstOrDefault();
public class Book
{
    [JsonProperty("title")]
    public string Title { get; set; }

    public Author Author { get; set; }

    public int Price { get; set; }

    public List<string> Publishers { get; set; }

}

public class Author
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
5
juvchan

Vous devriez essayer d'utiliser IEnumerable.Containslien ici

DbHelper.Client.CreateDocumentQuery<Art>(collection.DocumentsLink)
   .Where(i => i.type == "art")
   .Where(i => i.Products
       .Select(p => p.Name).Contains(productType))
                               .AsEnumerable()
                               .ToList();
2
mrtig

La solution la plus performante actuellement consiste à utiliser la syntaxe SQL car cela permet au document DB d'utiliser l'index de la collection.
exemple:

SELECT a 
  FROM a
  JOIN p in a.Products
 WHERE ARRAY_CONTAINS(a.Id, 'art') 
   AND p.Name = 'My Product Type'

L'inconvénient est que vous pouvez obtenir des résultats non uniques et devoir distinguer le résultat côté client.

Pour obtenir ce problème dans DocumentDB, il serait utile de voter sur l'élément suivant: https://feedback.Azure.com/forums/263030-documentdb/suggestions/14829654-support-sub-query-functions-like -existe-n'existe pas

1
TJ Galama

Pourquoi n'essayez-vous pas celui-ci?

 List<Art> items =  DocumentDbHelper.Client.CreateDocument(collection.DocumentsLink)
                           .Where(i => i.type == "art" && i.Products.Any(p => p.Name == productType))
                           .AsEnumerable()
                           .ToList();
0
Jose Ortega