web-dev-qa-db-ja.com

C#LINQ .DocumentDbで動作しないCreateDocumentQuery

特定のタイプの製品があるアートを照会しようとしています。これが私のアートのモデルです。

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

ここからは、次のLINQクエリのみを実行します。

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

次のエラーが表示されます。

"Method 'Any' is not supported."

サポートされているもの を参照するためにコードが参照しているページに行きましたが、Any()がサポートされていないということはわかりません。どんな助けも大歓迎です。

[〜#〜] update [〜#〜]

これは本当に奇妙なことなので、2つの結果から何が返されているのかを確認するために、問題をよりよくデバッグするために解散しました。

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

何らかの理由でこれは機能しますが、リストに変換しているのでクエリを2回実行しているため、私はこれのファンではありません-しかし、少なくともAny()とSelect()が技術的に機能するはずです。

25
gregwhitworth

_IQueryable<T>_に対するLINQクエリの最大の混乱の1つは、_IEnumerable<T>_に対するクエリとまったく同じに見えることです。前者は_Expression<Func<..>>_を使用していますが、後者は_Func<..>_を使用していますが、明示的な宣言を使用している場合を除き、これはそれほど目立たず、重要ではないようです。ただし、実行時に大きな違いが生じます。 _IEnumerable<T>_クエリが正常にコンパイルされると、実行時に正常に機能しますが、これは_IQueryable<T>_の場合とは異なります。 _IQuaryable<T>_クエリは、実際にはクエリプロバイダーによって実行時に処理される式ツリーです。クエリプロバイダーはクエリのコンパイル時に関与しないため(一方のメソッドはQueryableクラスによって拡張メソッドとして提供されます)、一方からはこれは他方からは大きな利点です。プロバイダーが実行時まで何らかの構成/メソッドをサポートしているかどうか。 Linq to Entitiesを使用している人々は、それをよく知っています。物事を難しくするために、特定のクエリプロバイダーがサポートするもの、さらに重要なことは、サポートしないもの(提供した「サポート対象」リンクからわかるように)には明確なドキュメントがありません。

解決策は何ですか(および2番目のコードが機能する理由)

トリックは、_IQueryable<T>_に対して可能な限り(クエリプロバイダーでサポートされている)クエリパーツを記述し、_IEnumerable<T>_に切り替えて残りを実行することです(コンパイルしたら、_IEnumerable<T>_クエリは機能します)。切り替えは、AsEnumerable()呼び出しによって実行されます。それが、2番目のコードが機能している理由です-サポートされていないAnyメソッドがDocumentDbクエリプロバイダーコンテキストにないためです。 ToList呼び出しは不要であり、クエリは2回実行されないことに注意してください。実際、この方法では1つのクエリではなく、2つ-データベースに1つ、メモリに1つです。したがって、このようなもので十分です

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

最後に、DocumentDbクエリプロバイダーで実際にサポートされているもの

ドキュメントからは明確ではありませんが、答えは次のとおりです。正確に(そして唯一の)そこに含まれるもの。言い換えると、サポートされている唯一のクエリ演算子(またはQueryableまたはEnumerable拡張メソッドを言う)は

  • 選択する
  • SelectMany
  • どこ
  • OrderBy
  • OrderByDescending

ご覧のとおり、非常に限られています。結合演算子とグループ化演算子、AnyContainsCountFirstLastなどは忘れてください。唯一の良い点は、覚えやすい:)

それをどうやって知るのですか?さて、いつものように、ドキュメントから何かが不明な場合は、試行錯誤または逆コンパイラを使用します。この場合、明らかに前者は適用されないので、後者を使用しました。興味がある場合は、お気に入りのデコンパイラを使用して、_Microsoft.Azure.Documents.Client.dll_内の内部クラスDocumentQueryEvaluatorのコードを確認してください。

75
Ivan Stoev

.Net 4.6をターゲットとする最新のAzure DocumentDB nugetを使用しています。

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

これが私のためにうまく機能しているサンプルコードです。

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

IEnumerable.Containsリンクはこちら を使用してみてください。

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

現在、最もパフォーマンスの高いソリューションは、SQL構文を使用することです。これにより、ドキュメントDBがコレクションのインデックスを使用できるようになります。
例:

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

欠点は、一意でない結果が得られる可能性があり、結果をクライアント側で区別する必要があることです。

この問題をDocumentDBに取り込むには、次の項目に投票すると役立ちます。 https://feedback.Azure.com/forums/263030-documentdb/suggestions/14829654-support-sub-query-functions-like -exists-not-exist

1
TJ Galama

これを試してみませんか?

 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