#c# #function #asp.net-core #odata #unbound
#c# #функция #asp.net-core #odata #несвязанная
Вопрос:
Я пытался реализовать что-то с помощью odata и ASP.NET Ядро 3, которое просто не хочет работать должным образом, и я, похоже, не могу понять, что не так. Я создал небольшой пример приложения для демонстрации.
У меня есть служба odata, которую я могу использовать для запроса узлов. Узлы могут быть узлами типа 1 или типа 2, и это открытые типы с динамическими свойствами. Запрос к ним работает отлично. Что я хочу сделать, так это вычислить пути между узлами. Пути не являются объектами — у них нет идентификатора. Поэтому я не считаю, что было бы правильно создавать ресурс для этого. Это просто результаты вычислений пути, содержащие списки узлов, которые находятся вдоль пути, поэтому я думаю, что функция — лучший способ сообщить 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", CustomProperties = new Dictionary<string, object>{{"foo", "bar"}}},
new Type2Node{Id = "third", Kind="type2-kind1", Source = "first", Target = "second"},
new Type2Node{Id = "fourth", Kind="type2-kind1", Source = "first", Target = "second", CustomProperties = new Dictionary<string, 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, TargetId = target, Nodes = new List<Node> {Nodes[0], 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=Aamp;target=B
должно вернуться 2 пути, и это происходит. Но есть только два строковых свойства, свойство collection отсутствует:
GET
host:port/odata/CalculatePaths?source=Aamp;target=B
вернет: {"@odata.context":"http://host:port/odata/$metadata#Collection(OdataSample.Path)","value":[{"SourceId":"A","TargetId":"B"},{"SourceId":"A","TargetId":"B"}]}
Я пробовал возиться с этим множеством разных способов, но без радости. Единственный раз, когда я приблизился к тому, что я хочу, был, когда я изменил путь, чтобы иметь только идентификаторы узлов (строку) вместо узлов. Но это не идеально, так как мне нужно было бы затем запросить отдельные узлы, хотя у меня уже есть вся необходимая информация.
Что я должен изменить, чтобы в ответе также отображались узлы?
Ответ №1:
Я попробовал ваши коды и получил тот же результат, который не удалось включить Nodes
в результат. Я внес небольшое изменение в EntitySet<Path>
чтобы получить Nodes
хорошо, но понятия не имею, подходит ли это для u или нет.
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; }
}
Результат, который вы ожидаете
Ссылки по теме: Свойство навигации в сложном типе
Комментарии:
1. спасибо за ваш ответ. Да, если я создам объекты paths, я смогу развернуть узлы и получить данные. Но семантически это неверно. Пути для меня не являются объектами, они просто отбрасываемые результаты вычислений. Я не хочу управлять их жизненным циклом, и они сопоставимы по значению (A-> B-> C == A->B-> C, независимо от того, когда были рассчитаны результаты). Поэтому создание их сущностей и назначение идентификаторов GUID только для того, чтобы не заботиться о них, похоже на обходной путь. Если это ограничение в технологии, я отмечу ваш ответ как решение, но я все еще надеюсь на что-то другое.
2. Вам удобно возвращать
Path
результат как объект. После завершения вычисления с ресурсами пути используйтеOdata
для запроса, настройки и представления данных.