如何解决ODATA:未绑定函数返回带有集合的复杂类型
我一直在尝试使用odata和ASP.NET Core 3实施某些事情,这些事情只是不想正常工作,而且我似乎无法找出问题所在。我创建了一个小示例应用程序进行演示。
我有一个odata服务,可用于查询节点。节点可以是 type1 或 type2 节点,它们是具有动态属性的开放类型。查询它们非常完美。 我想做的是计算节点之间的路径。路径不是实体-它们没有身份。因此,我认为为此创建资源是不正确的。它们只是路径计算的结果,其中包含沿路径的节点列表,因此我认为函数是一种更好的方式来告诉API我想要的东西。
因此,我创建了一个odata函数来进行计算并返回可用路径,并且它可以工作,除了无法获取它来返回路径正在遍历的节点列表(这是我真正需要的信息)之外。
我创建了一些示例代码来演示该问题:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.OData;
using Microsoft.AspNet.OData.Builder;
using Microsoft.AspNet.OData.Extensions;
using Microsoft.AspNet.OData.Routing;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace OdataSample
{
public static class Program {
public static void Main(string[] args) {
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
public class Startup {
public void ConfigureServices(IServiceCollection services) {
services.AddOData();
services.AddSingleton<IDataProvider,DataProvider>();
services.AddMvc(options => options.EnableEndpointRouting = false);
}
public void Configure(IApplicationBuilder app,IWebHostEnvironment env) {
app.UseDeveloperExceptionPage();
var builder = new ODataConventionModelBuilder(app.applicationservices);
builder.EntitySet<Node>("Nodes");
builder.ComplexType<Path>()
.HasMany(x => x.Nodes)
.HasDerivedTypeConstraints(typeof(Type1Node),typeof(Type2Node));
var calculatePath = builder.Function("CalculatePaths");
calculatePath.Parameter<string>("source");
calculatePath.Parameter<string>("target");
calculatePath.ReturnsCollection<Path>();
app.UseMvc(routeBuilder =>
{
routeBuilder.EnableDependencyInjection();
routeBuilder.Select().Expand().Filter().OrderBy().MaxTop(10).Count();
routeBuilder.MapODataServiceRoute("ODaTaroute","odata",builder.GetEdmModel());
});
}
}
public abstract class Node {
public string Id { get; set; }
public string Kind { get; set; }
public IDictionary<string,object> CustomProperties { get; set; }
}
public sealed class Type1Node : Node {
}
public sealed class Type2Node : Node {
public string Source { get; set; }
public string Target { get; set; }
}
public sealed class Path {
public string SourceId { get; set; }
public string TargetId { get; set; }
public List<Node> Nodes { get; set; }
}
public interface IDataProvider {
Task<IEnumerable<Node>> GetNodes();
Task<IEnumerable<Path>> GetPaths(string source,string target);
}
public sealed class DataProvider : IDataProvider {
private static readonly IList<Node> Nodes = new List<Node> {
new Type1Node{Id = "first",Kind="type1-kind1",CustomProperties = new Dictionary<string,object>()},new Type1Node{Id = "second",Kind = "type1-kind2",object>{{"foo","bar"}}},new Type2Node{Id = "third",Kind="type2-kind1",Source = "first",Target = "second"},new Type2Node{Id = "fourth",Target = "second",object>{{"red","blue"}}}
};
public async Task<IEnumerable<Node>> GetNodes() {
await Task.Yield();
return Nodes.ToList();
}
public async Task<IEnumerable<Path>> GetPaths(string source,string target) {
await Task.Yield();
return new List<Path> {
new Path { SourceId = source,TargetId = target,Nodes = new List<Node> {Nodes[0],Nodes[2],Nodes[1]}},new Path { SourceId = source,Nodes[3],Nodes[1]}}};
}
}
public class NodesController : ODataController {
private readonly IDataProvider dataProvider;
public NodesController(IDataProvider dataProvider) => this.dataProvider = dataProvider;
[EnableQuery]
public async Task<List<Node>> Get() => (await dataProvider.GetNodes()).ToList();
}
public class PathsController : ODataController {
private readonly IDataProvider dataProvider;
public PathsController(IDataProvider dataProvider) => this.dataProvider = dataProvider;
[EnableQuery]
[HttpGet]
[ODaTaroute("CalculatePaths")]
public async Task<List<Path>> Get(string source,string target) =>
(await dataProvider.GetPaths(source,target)).ToList();
}
}
很抱歉,我试图尽可能地压缩它。
现在http://host:port/odata/CalculatePaths?source=A&target=B
应该返回2条路径,并且确实如此。但是只有两个字符串属性,而collection属性不是:
GET
host:port/odata/CalculatePaths?source=A&target=B
将返回:
{"@odata.context":"http://host:port/odata/$Metadata#Collection(OdataSample.Path)","value":[{"SourceId":"A","TargetId":"B"},{"SourceId":"A","TargetId":"B"}]}
我尝试了很多不同的方式来解决它,但是却没有喜悦。我唯一接近所需的时间是将Path更改为仅包含节点ID(字符串)而不是节点。但这并不理想,因为即使我已经拥有所需的所有信息,我也需要查询各个节点。
我应该更改什么,以便在响应中也出现节点?
解决方法
我尝试了您的代码,但结果相同,但未能在结果中包含Nodes
。我对EntitySet<Path>
进行了一些改动,以使Nodes
正常运行,但不知道它是否适合您。
builder.EntitySet<Path>("CalculatePaths");
//builder.ComplexType<Path>()
// .HasMany(x => x.Nodes)
// .HasDerivedTypeConstraints(typeof(Type1Node),typeof(Type2Node));
需要将密钥Id
添加到Path
实体。
public sealed class Path
{
public int Id { get; set; }
public string SourceId { get; set; }
public string TargetId { get; set; }
public List<Node> Nodes { get; set; }
}
您期望的结果
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。