Подтвердить что ты не робот

С# LINQ. Не работает на DocumentDb CreateDocumentQuery

Я пытаюсь запросить Art, у которого есть продукт определенного типа. Вот моя модель для искусства:

  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

Это действительно странно для меня, поэтому я разбил его, чтобы увидеть, что было возвращено из двух результатов, чтобы лучше отладить проблему:

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

По какой-то причине это работает, я не являюсь поклонником этого, потому что, поскольку я преобразовываю его в список, который выполняет запрос дважды, но он по крайней мере доказал, что Any() и Select() должны технически работать.

4b9b3361

Ответ 1

Одна из самых больших путаниц в запросах LINQ против IQueryable<T> заключается в том, что они выглядят точно так же, как запросы от IEnumerable<T>. Ну, первый использует Expression<Func<..>> всякий раз, когда позже использует Func<..>, но кроме случаев использования явных деклараций это не так заметно и кажется несущественным. Однако большая разница возникает во время выполнения. Как только запрос IEnumerable<T> успешно скомпилирован, во время выполнения он просто работает, что не соответствует IQueryable<T>. Запрос IQuaryable<T> - это фактически дерево выражений, которое обрабатывается во время выполнения поставщиком запроса. С одной стороны это большое преимущество, с другой стороны, поскольку поставщик запросов не участвует во время компиляции запросов (все методы предоставляются в виде методов расширения классом Queryable), нет способа узнать, является ли поставщик поддерживает некоторую конструкцию/метод или нет до выполнения. Люди, которые используют Linq для Entities, знают это очень хорошо. Чтобы сделать все сложнее, нет четкой документации, которую поддерживает конкретный поставщик запросов и что еще более важно, что он не поддерживает (как вы заметили из ссылки "что поддерживается" ).

Какое решение (и почему работает ваш второй код)

Хитрость заключается в том, чтобы написать максимально возможную (запрошенную поставщиком запроса) часть запроса против IQueryable<T>, а затем переключиться на IEnumerable<T> и сделать остальное (помните, как только скомпилированный, IEnumerable<T> запрос работает просто). Переключатель выполняется вызовом AsEnumerable(). И почему ваш второй код работает, потому что неподдерживаемый Any метод больше не встречается в контексте поставщика запросов DocumentDb. Обратите внимание, что вызов ToList не нужен, и запрос не выполняется дважды - на самом деле так нет единого запроса, а два - один в базе данных и один в памяти. Так что чего-то подобного было бы достаточно

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

Как вы можете видеть, он очень ограничен. Забудьте о операторах объединения и группировки, Any, Contains, Count, First, Last и т.д. Единственное, что легко запомнить:

Откуда я знаю это? Ну, как обычно, когда что-то неясно из документации, либо используется пробная версия, либо ошибка или декомпилятор. По-видимому, в этом случае первое не применимо, поэтому я использовал это позже. Если вам интересно, используйте свой любимый декомпилятор и проверьте код внутреннего класса DocumentQueryEvaluator внутри Microsoft.Azure.Documents.Client.dll.

Ответ 2

Я использую последнюю версию Azure DocumentDB nuget.Net 4.6.

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

Ответ 3

Вам следует попробовать использовать ссылку 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();

Ответ 4

Наиболее эффективным решением в настоящее время является использование синтаксиса 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

Ответ 5

Почему бы вам не попробовать этот?

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