2015-11-21 3 views
17

Ich versuche, Art, die ein Produkt eines bestimmten Typs hat, abzufragen. Hier ist mein Modell für Kunst:C# LINQ. Arbeitet nicht auf DocumentDb CreateDocumentQuery

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

Von hier alles, was ich zu tun habe, ist die folgende LINQ-Abfrage:

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

ich die folgende Fehlermeldung erhalten:

"Method 'Any' is not supported." 

Ich ging zu die Seite, auf die der Code verweist, um what is supported zu sehen, aber ich sehe nicht, dass Any() nicht unterstützt wird, also mache ich wahrscheinlich etwas falsches. Jede Hilfe wird geschätzt.

UPDATE

Das ist mir wirklich seltsam, so brach ich es zu sehen, was aus den beiden Ergebnissen besser Debug das Problem zu diesem Retour wurde: Für einige

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

Warum das funktioniert, bin ich kein Fan davon, da ich es in eine Liste umwandle, führt es die Abfrage zweimal aus - aber es ist zumindest der Beweis, dass Any() und Select() technisch funktionieren sollten.

+0

Hallo, sind Sie in der Lage hier, um die Details Fehler zu setzen? – juvchan

+0

Ich habe es hinzugefügt, aber es wird nicht viel mehr dazu hinzufügen. Die Quelle des Fehlers ist Microsoft.Azure.Documents.Client – gregwhitworth

+0

Wird in der Dokumentation angegeben, dass es unterstützt wird? Ich glaube nicht, dass Sie davon ausgehen sollten, dass es nur deshalb ist, weil sie nicht explizit erwähnt haben, dass es nicht so ist. – MarcinJuraszek

Antwort

47

Eines der größten Verwirrung mit LINQ-Abfragen gegen IQueryable<T> ist, dass sie sehen genau das gleiche wie Abfragen für IEnumerable<T>. Nun, der erstere verwendet Expression<Func<..>>, wenn der spätere Func<..> verwendet, aber außer wenn man explizite Deklarationen verwendet, ist dies nicht so auffällig und scheint unwichtig zu sein. Der große Unterschied kommt jedoch zur Laufzeit. Sobald die IEnumerable<T> Abfrage erfolgreich kompiliert wurde, funktioniert es zur Laufzeit einfach, was bei IQueryable<T> nicht der Fall ist. Eine IQuaryable<T> Abfrage ist eigentlich ein Ausdrucksbaum, der zur Laufzeit vom Abfrageprovider verarbeitet wird. Auf der einen Seite ist dies ein großer Vorteil, auf der anderen Seite, da der Abfrageprovider nicht an der Kompilierungszeit der Abfrage beteiligt ist (alle Methoden werden von der Klasse Queryable als Erweiterungsmethode bereitgestellt). Es gibt keine Möglichkeit zu wissen, ob der Provider einige unterstützt Konstruieren/Methode oder nicht bis zur Laufzeit. Leute, die Linq zu Entitäten benutzen, wissen das sehr gut. Um die Dinge schwieriger zu machen, gibt es keine klare Dokumentation, was der spezifische Abfrageanbieter unterstützt und was noch wichtiger ist, was er nicht unterstützt (wie Sie aus dem Link "Was wird unterstützt?" Erfahren haben).

Was die Lösung ist (und warum Ihr zweiter Code funktioniert)

Der Trick ist, die maximal möglichen zu schreiben (dvom Abfrageprovider unterstützt) Abfrage Teil gegen die IQueryable<T>, und wechseln Sie dann zu IEnumerable<T> und den Rest tun (Denken Sie daran, einmal kompiliert, IEnumerable<T> Abfrage funktioniert einfach). Die Umschaltung erfolgt durch AsEnumerable() Anruf. Und deshalb funktioniert Ihr zweiter Code - weil die nicht unterstützte Any Methode nicht mehr im DocumentDb Query Provider Kontext ist. Beachten Sie, dass ToList Aufruf nicht benötigt wird und die Abfrage nicht zweimal ausgeführt wird - tatsächlich gibt es auf diese Weise keine einzelne Abfrage, sondern zwei - eine in der Datenbank und eine im Speicher. So etwas wie dies würde ausreichen, um

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

Schließlich, was wirklich von der DocumentDb Abfrage Anbieter unterstützt wird

Es ist nicht ganz klar, aus der Dokumentation, aber die Antwort ist: genau (und nur) was dort enthalten ist. Mit anderen Worten, die nur unterstützt Abfrageoperatoren (oder besser sagen Queryable oder Enumerable Erweiterungsmethoden) sind

  • Select
  • Select
  • Wo
  • SortiertNach
  • OrderByDescending

Wie Sie können sehen, es ist sehr begrenzt. Vergessen beitreten und Gruppierungsoperator, Any, Contains, Count, First, Last usw. Die einzig Gute daran ist, dass es einfach speicherbaren :)

Wie kann ich wissen, dass? Nun, wie üblich, wenn etwas aus der Dokumentation unklar ist, verwenden Sie entweder Versuch und Irrtum oder Decompiler. Offensichtlich ist in diesem Fall der erstere nicht anwendbar, also habe ich den späteren verwendet. Wenn Sie neugierig sind, verwenden Sie Ihren bevorzugten Decompiler und überprüfen Sie den Code der internen Klasse DocumentQueryEvaluator innerhalb der Microsoft.Azure.Documents.Client.dll.

+1

Ähnliches passierte mir mit Unit Tests gegen reale Umgebung Spott über den Kontext mit einem IEnumerable <>, um die Daten so zu halten. Any() funktioniert auf dem Komponententest. Veröffentlicht es zu testen und Boom: Ausnahme. –

+1

Vielen Dank yo Du bist so sehr - das war in der Tat das Problem und ich schätze es sehr, dass du nicht nur die Lösung, sondern auch die Begründung dafür lieferst: "Lehre einen Mann zum Fischen ..." und all das. Vielen Dank!! – gregwhitworth

+6

Ich möchte nur hinzufügen, bitte beachten Sie, dass eine Reihe neuer LINQ-Operatoren in DocumentDB unterstützt werden, einschließlich der Operatoren Math, String, Spatial und Array (einschließlich Contains). Details hier: https://azure.microsoft.com/en-us/blog/azure-documentdb-s-linq-provider-just-got-better/ –

3

Ich benutze die neueste Azure DocumentDB nugget targetting .Net 4.6.

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

Hier ist der Beispielcode, der für mich in Ordnung ist.

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; } 
} 
+0

Ich füge bereits System.Linq hinzu. – gregwhitworth

+0

Ich benutze alle die gleichen Sachen, .NET 46, DB Version 1.5 und sogar Ihr Beispiel Umschalten ToList() zu FirstOrDefault() funktioniert nicht und löst die Aggregatausnahme, dass Any() nicht unterstützt wird. Ich stimme zu, dass es funktionieren sollte, und sagte oben, dass, wenn ich nur das erste Where() hole und dann Any() anwendet, es funktioniert, scheint es die Verkettung zu sein. Irgendwelche Ideen? – gregwhitworth

+0

Wie ist die Visual Studio-Version und das Azure SDK auf Ihrer Plattform installiert? – juvchan

2

sollten Sie versuchen, IEnumerable.Containslink here

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

Ich bekomme den gleichen Fehler - jetzt nur mit Select() wird nicht unterstützt. Das ist sehr seltsam. – gregwhitworth

+0

Interessant. Was passiert, wenn Sie die Auswahl ändern, so dass es erzeugt zuerst einen anonymen Typ, dann benutze .Any? – mrtig

+0

Ich habe das selbe gemacht, ich habe es nicht als anonymen Typ erzeugt, siehe mein Update in der ersten Frage. Das scheint zu funktionieren. Ich denke, ich sollte diesen Namen auch auf dem Produkt angeben ist eine Zeichenkette, aber technisch sollte das nicht von viel Konsequenz sein, da die Folgeabfrage funktioniert, es ist die Verkettung, die das Problem verursacht. – gregwhitworth

0

Warum versuchst du das nicht?

1

Die leistungsstärkste Lösung ist derzeit die Verwendung der SQL-Syntax, da Dokumentdatenbank den Index der Sammlung verwenden kann.
Beispiel:

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

Der Nachteil ist, dass Sie nicht eindeutige Ergebnisse und das Ergebnis Client-Seite zu deutlicher haben könnten bekommen.

, um dieses Problem in DocumentDB zu bekommen, wäre es zu dem folgenden Punkt wählen helfen: https://feedback.azure.com/forums/263030-documentdb/suggestions/14829654-support-sub-query-functions-like-exists-not-exist