Как представить табличную функцию как свойство набора сущностей в службе OData v4 Web Api 2?

Пожалуйста, мне нужна помощь в выяснении того, как предоставить табличную функцию как свойство объекта, установленного в службе OData v4 Web Api 2.

В моей упрощенной схеме есть три таблицы: Structures, Locations и LocationLinks. Структура содержит граф с узлами (Locatons) и ребрами (LocationLinks). Я обращаюсь к первой модели базы данных Entity Framework 6.

Simplified Schema

  Structure:
      ID

  Locations:
      ID
      ParentID -> Structure

  LocationLinks
      A -> Location
      B -> Location

Цель состоит в том, чтобы получить доступ к коллекции структур LocationLink так же, как я получаю доступ к местоположению структуры. то есть, чтобы запросить график структуры №180:

http://.../OData/Structures(180)/LocationLinks
http://.../OData/Structures(180)/Locations

Запрос Locations работает автоматически, но я не могу понять, как добавить правильный маршрут для включения запроса LocationLinks. Думая, что это упростит мою задачу, я добавил на свой SQL-сервер табличную функцию. Функция присутствует в моей модели EF и возвращает коллекцию объектов LocationLink:

StructureLocationLinks(@StructureID) -> LocationLinks

К сожалению, что бы я ни пытался, мне кажется, что URL-адрес Structure (180) \ LocationLinks не работает. Это моя последняя попытка:

Фрагмент StructuresController.cs:

        // GET: odata/Structures(5)/Locations
        [EnableQuery]
        public IQueryable<Location> GetLocations([FromODataUri] long key)
        {
            return db.Structures.Where(m => m.ID == key).SelectMany(m => m.Locations);
        }

        // GET: odata/Structures(5)/LocationLinks
        [EnableQuery]        
        //[System.Web.OData.Routing.ODataRoute("Structures({key})")]
        public IQueryable<LocationLink> GetLocationLinks([FromODataUri] long key)
        {
            return db.StructureLocationLinks(key);
        }

Фрагмент WebApi.cs:

public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services
            var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
            json.UseDataContractJsonSerializer = true;
            //json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.All;

            //var cors = new System.Web.Http.Cors.EnableCorsAttribute("*", "*", "*");
            //config.EnableCors(cors);

            // Web API routes 
            config.MapHttpAttributeRoutes();

            ODataConventionModelBuilder builder = GetModel();

            config.MapODataServiceRoute(routeName: "odata",
                routePrefix: null,
                model: builder.GetEdmModel());

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }

    public static ODataConventionModelBuilder GetModel()
        {
            ODataConventionModelBuilder builder = new ODataConventionModelBuilder();

            builder.Namespace = typeof(Structure).Namespace;

            AddLocationLinks(builder);
            AddStructures(builder);
            builder.EntitySet<Location>("Locations");

            return builder;
        }

        public static void AddStructures(ODataModelBuilder builder)
        {
            var structSetconfig = builder.EntitySet<Structure>("Structures");
            var structConfig = structSetconfig.EntityType;

            var functionConfig = structConfig.Collection.Function("StructureLocationLinks");
            functionConfig.Parameter<long>("StructureID");
            functionConfig.Returns<LocationLink>();
        }


        public static void AddLocationLinks(ODataModelBuilder builder)
        {
            var type = builder.EntityType<LocationLink>();
            type.HasKey(sl => sl.A);
            type.HasKey(sl => sl.B);
            builder.EntitySet<LocationLink>("LocationLinks");
        }

Я получаю следующую ошибку:

{"error": {"code": "", "message": "Не найден HTTP-ресурс, соответствующий URI запроса 'http://.../OData/Structures(180)/LocationLinks '.", "innererror": {"message": "Не найдено соглашение о маршрутизации для выбора действия для пути OData с шаблоном '~ / entityset / key / unresolved'. "," type ":" "," stacktrace ":" "}}}

Основываясь на некоторых поисках, я попытался добавить к контроллеру атрибут ODataRoute:

// GET: odata/Structures(5)/LocationLinks
[EnableQuery]        
[System.Web.OData.Routing.ODataRoute("Structures({key})/LocationLinks")]
public IQueryable<LocationLink> GetLocationLinks([FromODataUri] long key)
{
    return db.StructureLocationLinks(key);
}

Что приводит к этой ошибке:

Шаблон пути Structures ({key}) / LocationLinks в действии GetLocationLinks в контроллере Structures не является допустимым шаблоном пути OData. Обнаружен неразрешенный сегмент пути "LocationLinks" в шаблоне пути OData "Structures ({key}) / LocationLinks".

Как я могу сделать доступными LocationLinks из коллекции Structures? Спасибо за уделенное время.

Изменить:

Мне удалось заставить это работать после того, как я нашел этот вопрос: Добавление пользовательского свойство навигации с поддержкой запроса к ODataConventionModelBuilder

Мне пришлось вызвать .GetEdmModel для моего объекта ODataConventionBuilder, а затем добавить свойство навигации в модель с помощью этой функции:

private static Microsoft.OData.Edm.IEdmModel AddStructureLocationLinks(IEdmModel edmModel)
        { 

            var structures = edmModel.EntityContainer.FindEntitySet("Structures") as EdmEntitySet;
            var locationLinks = edmModel.EntityContainer.FindEntitySet("LocationLinks") as EdmEntitySet;
            var structType = structures.EntityType() as EdmEntityType;
            var locLinksType = locationLinks.EntityType() as EdmEntityType;

            var structLocLinksProperty = new EdmNavigationPropertyInfo();
            structLocLinksProperty.TargetMultiplicity = Microsoft.OData.Edm.EdmMultiplicity.Many;
            structLocLinksProperty.Target = locLinksType;
            structLocLinksProperty.ContainsTarget = true; 
            structLocLinksProperty.OnDelete = Microsoft.OData.Edm.EdmOnDeleteAction.None;
            structLocLinksProperty.Name = "LocationLinks";

            var navigationProperty = structType.AddUnidirectionalNavigation(structLocLinksProperty);
            structures.AddNavigationTarget(navigationProperty, locationLinks);

            return edmModel; 
        }

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

http://.../OData/Structures(180)/Children?$expand=Locations

Пока этого нет.

http://.../OData/Structures(180)/Children?$expand=LocationLinks

Возвращенная ошибка

{"error": {"code": "", "message": "Произошла ошибка.", "innererror": {"message": "Свойство экземпляра 'LocationLinks' не определено для типа 'ConnectomeDataModel.Structure' "," тип ":" System.ArgumentException "," stacktrace ":" в System.Linq.Expressions.Expression.Property (выражение выражения, String propertyName) \ r \ n в System.Web.OData.Query.Expressions.SelectExpandBinder .CreatePropertyValueExpressionWithFilter (IEdmEntityType elementType, свойство IEdmProperty, источник выражения, FilterClause filterClause) \ r \ n в System.Web.OData.Query.Expressions.SelectExpandBinder.BuildPropertyContainer (IEdmEntityType_элемент_EdmEntityType) (IEdmEntityType_элемент_элемент_IdmEntity1 \ n в System.Web.Http.Controllers.ActionFilterResult.d__2.MoveNext () \ r \ n --- Конец трассировки стека из предыдущего места, где было сгенерировано исключение --- \ r \ n в System.Runtime.CompilerServices. TaskAwaiter.ThrowForNonSuccess (Задача задача) \ r \ n в System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (Task task) \ r \ n в System.Runtime.CompilerServices.TaskAwaiter`1.GetResult () \ r \ n в System.Web.Http.DispatcherControl.Http. d__1.MoveNext () "}}}


person Xycor    schedule 03.09.2015    source источник


Ответы (1)


Xycor

Как вы сказали, LocationLinks должен быть таким же, как Locations для Structure. Итак, то, что вы сделали для контроллера и действия, я считаю правильным. У меня есть тест на основе вашего описания, и кажется, OData веб-API может маршрутизировать GetLocationLinks по соглашению.

Позвольте мне показать мой документ метаданных, пожалуйста, не обращайте внимания на пространство имен, сравните его с вашим и дайте мне знать о любых различиях. Спасибо.

<Schema Namespace="ODataConsoleSample" xmlns="http://docs.oasis-open.org/odata/ns/edm">
  <EntityType Name="LocationLink">
    <Key>
      <PropertyRef Name="A" />
      <PropertyRef Name="B" />
    </Key>
    <Property Name="A" Type="Edm.Int64" Nullable="false" />
    <Property Name="B" Type="Edm.Int64" Nullable="false" />
  </EntityType>
  <EntityType Name="Structure">
    <Key>
      <PropertyRef Name="Id" />
    </Key>
    <Property Name="Id" Type="Edm.Int64" Nullable="false" />
    <NavigationProperty Name="Locations" Type="Collection(ODataConsoleSample.Location)" />
    <NavigationProperty Name="LocationLinks" Type="Collection(ODataConsoleSample.LocationLink)" />
  </EntityType>
  <EntityType Name="Location">
    <Key>
      <PropertyRef Name="Id" />
    </Key>
    <Property Name="Id" Type="Edm.Int64" Nullable="false" />
    <Property Name="ParentId" Type="Edm.Int64" Nullable="false" />
  </EntityType>
  <Function Name="StructureLocationLinks" IsBound="true">
    <Parameter Name="bindingParameter" Type="Collection(ODataConsoleSample.Structure)" />
    <Parameter Name="StructureID" Type="Edm.Int64" Nullable="false" />
    <ReturnType Type="ODataConsoleSample.LocationLink" />
  </Function>
  <EntityContainer Name="Container">
    <EntitySet Name="LocationLinks" EntityType="ODataConsoleSample.LocationLink" />
    <EntitySet Name="Structures" EntityType="ODataConsoleSample.Structure">
      <NavigationPropertyBinding Path="Locations" Target="Locations" />
      <NavigationPropertyBinding Path="LocationLinks" Target="LocationLinks" />
    </EntitySet>
    <EntitySet Name="Locations" EntityType="ODataConsoleSample.Location" />
  </EntityContainer>
</Schema>
person Sam Xu    schedule 06.09.2015
comment
Это выглядит разумным. Однако я считаю, что мне нужно добавить свойство Navigation при настройке службы OData. EDMX, который у меня есть, создан из базы данных. Это функция импорта для EDMX. Когда я устанавливаю свойство IsBound = True для определения функции, я получаю ошибку, связанную с недопустимым свойством. - person Xycor; 07.09.2015
comment
Пожалуйста, проигнорируйте комментарий выше. Я опубликовал случайно, и к тому времени, когда я отредактировал его, он был заблокирован. Это правильный ответ: это выглядит разумным. Однако я считаю, что мне нужно добавить NavigationProperty в ODataConventionBuilder при настройке службы OData. EDMX, который у меня есть, создан из базы данных. В моем определении функции отсутствует свойство IsBound. Когда я устанавливаю свойство IsBound = True для определения функции, я получаю ошибку, связанную с недопустимым свойством. Кажется, есть и другие проблемы, которые я более подробно рассмотрю завтра. Спасибо, что взглянули на это. - person Xycor; 07.09.2015