从NetCoreApp2.1调用OData服务时出现System.NotSupportedException

从NetCoreApp2.1调用OData服务时出现System.NotSupportedException

问题描述:

我已经建立了一个多目标(net4.5.2/netstandard2)类库,可以使用我们的一项企业OData服务.要访问此OData服务,我们使用由 OData v4客户端代码生成器(v7.5.0)

I have set up a multi targetting (net4.5.2/netstandard2) class library allowing to consume one of our enterprise OData services. To access this OData service we use a proxy class generated with the OData v4 Client Code Generator (v7.5.0)

不幸的是,当尝试在Netcoreapp2.1应用程序中使用我的库时,尝试枚举集合时会遇到问题.

Unfortunately, when trying to use my library in a Netcoreapp2.1 application I encounter an issue as soon as I try to enumerate a collection.

Container.MyDataSet.ToList(); 产生以下异常:

"System.NotSupportedException:此目标框架未启用您可以直接枚举数据服务查询.这是因为枚举自动向数据发送同步请求服务.由于此框架仅支持异步操作,您必须改为调用BeginExecute和EndExecute方法以获取支持枚举的查询结果."

"System.NotSupportedException : This target framework does not enable you to directly enumerate over a data service query. This is because enumeration automatically sends a synchronous request to the data service. Because this framework only supports asynchronous operations, you must instead call the BeginExecute and EndExecute methods to obtain a query result that supports enumeration."

在.Net 4.5.2应用程序中使用相同的多目标库时,我没有遇到此问题.

I do not encounter this issue when using this same multitarget library in a .Net 4.5.2 application.

看一下Microsoft.OData.Client v7.5.0源代码,此行为似乎是设计使然并带有对.Net Core案例的特定处理.

Having a look at the Microsoft.OData.Client v7.5.0 source code, this behaviour seems to be by design with specific handling of the .Net Core case.

我错过了什么吗?

以下代码可防止此问题,但几乎无法使用:

The following code prevents the issue, but it is barely usable :

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;

如何在不添加特定代码的情况下在.Net Core应用程序中使用OData IQueryable?

How can I use an OData IQueryable in .Net Core application without adding specific code ?

如@PanagiotisKanavos所说, DataServiceQuery.ToString()将返回OData查询的uri.基于此,我编写了自己的 IQueryable :

As said by @PanagiotisKanavos DataServiceQuery.ToString() will return the uri of the OData query. Based on this, I wrote my own 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,处理身份验证令牌并执行结果反序列化.当我通过接口处理 IQueryables 时,FinalType会跟踪我想要获取和反序列化的类型.然后,我编写了自己的 IQueryProvider :

Where MyClient is an utility class which wraps an HttpClient, handles authentication token, and performs result deserialization. FinalType is to keep track on the type I want to obtain and deserialize, as I am handling IQueryables over interfaces. Then I wrote my own 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 "just"调用其底层的 GetAsync 方法具有正确身份验证标头的 HttpClient ,等待并反序列化结果:

Where Odata<> class is just for deserialization of the OData result and GetResult "just" invokes the GetAsync method of its underlying HttpClient with the correct authentication headers, wait for and deserializes the result :

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 公开如下:

Finally I expose my IQueryable as follows :

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

然后,我可以像使用标准 IQueryable 一样应用过滤器,orderby,top和skip并获取结果.我知道此实现尚不完整,对OData的 IQueryable 并不像对SQL的大多数 IQueryable 那样完整,但它达到了我所需要的最低要求.

I can then apply filters, orderby, top and skip and get the results as with a standard IQueryable. I know that this implementation is not complete, and IQueryable to OData is not as complete as most IQueryable to SQL, but it achieves the minimum I need.