System.NotSupportedException при вызове службы OData из NetCoreApp2.1 - PullRequest
0 голосов
/ 29 октября 2018

Я установил библиотеку классов с несколькими целями (net4.5.2 / netstandard2), позволяющую использовать один из наших корпоративных сервисов OData. Для доступа к этой службе OData мы используем прокси-класс, созданный с помощью Генератора клиентского кода OData v4 (v7.5.0)

К сожалению, при попытке использовать мою библиотеку в приложении Netcoreapp2.1 у меня возникает проблема, как только я пытаюсь перечислить коллекцию.

Container.MyDataSet.ToList(); выдает следующее исключение:

"System.NotSupportedException: эта целевая платформа не включает вам напрямую перечислить запрос службы данных. Это потому что перечисление автоматически отправляет синхронный запрос к данным оказание услуг. Поскольку эта структура поддерживает только асинхронные операции, вместо этого вы должны вызвать методы BeginExecute и EndExecute для получить результат запроса, который поддерживает перечисление. "

Я не сталкиваюсь с этой проблемой при использовании этой же многоцелевой библиотеки в приложении .Net 4.5.2.

Если взглянуть на исходный код Microsoft.OData.Client v7.5.0, то, похоже, это поведение спроектировано с особой обработкой случая .Net Core.

Я что-то пропустил?

Следующий код предотвращает возникновение проблемы, но его трудно использовать:

var query = (DataServiceQuery<MyData>)Container.MyDataSet;
var taskFactory = new TaskFactory<IEnumerable<MyData>>();
var t = taskFactory.FromAsync(query.BeginExecute(null, null), data => query.EndExecute(data));
t.ConfigureAwait(false);
IEnumerable<MyData> result = t.Result;

Как я могу использовать OData IQueryable в приложении .Net Core без добавления определенного кода?

Ответы [ 2 ]

0 голосов
/ 16 ноября 2018

Как сказал @PanagiotisKanavos DataServiceQuery.ToString() вернет URI запроса OData. Исходя из этого, я написал свой IQueryable:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

using Microsoft.OData.Client;

public class ODataLinqQuery<T> : IOrderedQueryable<T>
{
    public IQueryProvider Provider { get; }

    private DataServiceQuery<T> DataServiceQuery { get; }

    public ODataLinqQuery(DataServiceQuery<T> dataServiceQuery, MyClient client, Type finalType)
    {
        this.DataServiceQuery = dataServiceQuery;
        this.Provider = new ODataLinqQueryProvider<T>(dataServiceQuery, client, finalType);
    }

    public IEnumerator<T> GetEnumerator()
    {
        return this.Provider.Execute<IEnumerable<T>>(this.Expression).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.Provider.Execute<System.Collections.IEnumerable>(this.Expression).GetEnumerator();
    }

    public Expression Expression => this.DataServiceQuery.Expression;

    public Type ElementType => typeof(T);
}

Где MyClient - это служебный класс, который оборачивает HttpClient, обрабатывает токен аутентификации и выполняет десериализацию результатов. FinalType должен отслеживать тип, который я хочу получить и десериализовать, так как я обрабатываю IQueryables через интерфейсы. Тогда я написал свой IQueryProvider:

using System;
using System.Collections;
using System.Linq;
using System.Linq.Expressions;
using System.Net.Http;

using Microsoft.OData.Client;

public class ODataLinqQueryProvider<T> : IQueryProvider
{
    private MyClient Client { get; set; }

    private DataServiceQuery<T> DataServiceQuery { get; set; }

    private Type FinalType { get; }

    public ODataLinqQueryProvider(
        DataServiceQuery<T> dsq,
        MyClient client,
        Type finalType)
    {
        this.DataServiceQuery = dsq;
        this.Client = client;
        this.FinalType = finalType;
    }

    public IQueryable CreateQuery(Expression expression)
    {
        return new ODataLinqQuery<T>(this.DataServiceQuery, this.Client, this.FinalType);
    }

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    {
        var pro = new DataServiceQuery<TElement>(expression, this.DataServiceQuery.Provider as DataServiceQueryProvider);
        return new ODataLinqQuery<TElement>(pro, this.Client, this.FinalType);
    }

    public object Execute(Expression expression)
    {
        this.DataServiceQuery = new DataServiceQuery<T>(expression, this.DataServiceQuery.Provider as DataServiceQueryProvider);
        return this.Execute();
    }

    public TResult Execute<TResult>(Expression expression)
    {
        this.DataServiceQuery = new DataServiceQuery<T>(expression, this.DataServiceQuery.Provider as DataServiceQueryProvider);
        var res = this.Execute();
        if (typeof(IEnumerable).IsAssignableFrom(typeof(TResult)))
        {
            return (TResult)res;
        }
        else
        {
            return ((IEnumerable)res).Cast<TResult>().FirstOrDefault();
        }
    }

    private object Execute()
    {
        var result = Client.GetResult(typeof(OData<>).MakeGenericType(this.FinalType), HttpMethod.Get, new Uri(this.DataServiceQuery.ToString())) as OData;
        return result.Objects;
    }
}

Где Odata<> класс предназначен только для десериализации результата OData, а GetResult "просто" вызывает метод GetAsync его базового HttpClient с правильными заголовками аутентификации, ожидает и десериализует результат:

using System.Collections.Generic;

using Newtonsoft.Json;

public class OData<T> : OData where T : class
{
    public override IEnumerable<object> Objects => this.Value;

    public List<T> Value { get; set; }
}

public class OData
{
    [JsonProperty("@odata.context")]
    public string Metadata { get; set; }

    public virtual IEnumerable<object> Objects { get; set; }
}

Наконец я выставляю свой IQueryable следующим образом:

var myQueryable = new ODataLinqQuery<MyData>(this.Container.MyDataSet, myclient, typeof(MyData));

Затем я могу применить фильтры, orderby, top и skip и получить результаты как со стандартным IQueryable. Я знаю, что эта реализация не завершена, и IQueryable для OData не так полна, как большинство IQueryable для SQL, но достигает минимума, который мне нужен.

0 голосов
/ 13 ноября 2018

Как упоминалось в сообщении об ошибке, платформа поддерживает только асинхронные выборки. Даже после того, как вы воспользуетесь этим, вам, вероятно, потребуется многократно перечислять результаты - каждый раз, когда вы выполняете ToList(), FirstOrDefault() или другие подобные System.Generics.Collections операции, вы, по сути, получаете Enumerator коллекции и перечисляя его.

Я принял это решение: сразу после извлечения перечисляемых результатов из библиотек OData я перечисляю их и помещаю в другой перечисляемый контейнер (в данном случае Dictionary<string, MyAwesomeResult>), созданный мной.

var resultsQuery = this.oDataClient.MyAwesomeResults
    .AddQueryOption("$filter", "Name eq 'MyAwesomeName'")
    .AddQueryOption("$top", "5")
    .AddQueryOption("$skip", "2");

IEnumerable<MyAwesomeResult> resultsRaw = await 
resultsQuery.ExecuteAsync();
var results = new Dictionary<string, MyAwesomeResult>();`

foreach (var resultRaw in resultsRaw)
{
    results.Add(resultRaw.Key, resultRaw);
}

Затем я использую созданный мной контейнер - мне больше не нужно снова перечислять перечислимое, возвращаемое DataServiceQuery<MyAwesomeResult>.ExecuteAsync.

...