Как определить (необязательное) свойство навигации (?$ Expand=) в коде?

Сначала объяснение и суть, потом вопрос. Так:

Допустим, у меня есть вид AccountView сначала определяется в базе данных EF (6.1.1) (edmx), так что сгенерированный кодом класс

//This class is generated from a view by EF (edmx)...
public partial class AccountView
{
    public System.Guid Id { get; set; }
    public int CompanyId { get; set; }
}

Затем я создаю частичный класс в том же пространстве имен (Entities) как

[MetadataType(typeof(AccounViewMetaData))]
public partial class AccounView
{
    //This is added here explicitly. AccountView itself exposes just
    //a naked key, CompanyId.
    public virtual Company Company { get; set; }

    //This is just in case...
    public class AccounViewDomainMetaData
    {
        //This is to add a navigation property to the OData $metadata. How to do this
        //in WebApiConfig? See as follows...
        [ForeignKey("Company")]
        public int CompanyId { get; set; }
    }
}

а также

//This is an EF generated class one from an edmx..-
public partial class Company
{
    public Company()
    {            
    }

    public int CompanyID { get; set; }
    public string Name { get; set; }
}  

А потом в WebApiConfig Пишу или пытаюсь что-то сделать в OData v4 (новейшей версии 6.9.0.0 с WebApi, также самой новой) следующим образом

builder.EntitySet<Entities.Company>("Companies");

var accountSet = builder.EntitySet<Entities.AccountView>("Accounts");
accountSet.EntityType.HasKey(i => i.Id); //EF has hard time recognizing primary keys on database first views...

//TODO: How should I define the required binding so that I could "?$expand=Company"
//as such as ``http://example.com/service/Accounts?$expand=Company``
//accountSet.HasRequiredBinding(i => i.CompanyId, typeof(Entities.Company));

Результирующий $metadata как

<schema>
    <EntityType Name="Company">
        <Key>
            <PropertyRef Name="CompanyID"/>
        </Key>
        <Property Name="CompanyID" Type="Edm.Int32" Nullable="false"/>
        <Property Name="Name" Type="Edm.String"/>
     </EntityType>

    <EntityType Name="AccountView">
        <Key>
            <PropertyRef Name="Id"/>
        </Key>
        <Property Name="Id" Type="Edm.Guid" Nullable="false"/>
        <Property Name="CompanyId" Type="Edm.Int32" Nullable="false"/>
        <NavigationProperty Name="Company" Type="Entities.Company" Nullable="false"/>
    </EntityType>
</Schema>

Вопрос (ы): Как указано в TODO комментарий, как мне определить

  • Навигационное свойство, чтобы я мог позвонить http://example.com/Accounts?$expand=Company или же http://example.com/Accounts?$Companies?

Теперь это работает, если я позвоню

  • http://example.com/Accounts или же
  • http://example.com/Accounts(someId)

Тогда, если я сделаю что-то вроде

  • http://example.com/Accounts?$expand=Companies
  • http://example.com/Accounts?$expand=Company или же
  • http://example.com/Accounts(someId)?$expand=Company

Меня приветствуют по HTTP 400 (?$ Expand= Компании) или HTTP 500 (?$ Expand=Company).

Мне удалось создать Containment отношения уже. Похоже, однако, что для этого требуется, чтобы корневая сущность была определена с помощью идентификатора, тогда как я хотел бы предоставить "GET" для корня и при желании расширить до "дочернего списка" объектов (отсюда этот вопрос о?$ Expand=),

Теперь дело имеет один необязательный expand сделано для одной сущности, но я подозреваю, что следующая вещь, которую я хотел бы иметь, это сценарий, в котором есть список сущностей (или компаний в терминах этого конкретного примера), но как я могу достичь этих сценариев? Как исправить даже этот случай расширения до первого всегда существующего подобъекта?

Мой контроллер определяется следующим образом

public class AccountsController: ODataController
{
    [EnableQuery]
    public IHttpActionResult Get()
    {
        try
        {
            //This context here is a EF entities model (generated from an edmx)...
            return Ok(Context.AccountView);
        }
        catch(Exception ex)
        {
            return InternalServerError(); 
        }
    }

    [EnableQuery]
    public IHttpActionResult Get([FromODataUri]Guid key)
    {
        try
        {
            return Ok(SingleResult.Create(Context.AccountView.Where(a => a.Id == key)));
        }
        catch (Exception ex)            
        {                
            return InternalServerError(ex); 
        }
    }
}

В основном я пытаюсь сначала добавить в базу данных еще несколько метаданных, edmx, views и подражать статье Используя $select, $expand и $ value в OD. ASP.NET Web API 2. Пока безуспешно...

сюжет утолщается, так сказать. Ошибка HTTP 500 происходит из-за внутреннего исключения (мне пришлось включить разрыв в управляемые исключения среды), которое говорит

Первое случайное исключение типа "System.NotSupportedException" произошло в EntityFramework.dll

Дополнительная информация: указанный тип члена "Компания" не поддерживается в LINQ to Entities. Поддерживаются только инициализаторы, элементы сущностей и свойства навигации сущностей.

Так да, AccountView это представление, и оно не имеет прямого отношения внешнего ключа к Company таблица (которая просто является таблицей, но это тоже может быть представление). Как я могу пойти, чтобы добавить один? Я, хотя добавление метаданных уже добился цели, о чем свидетельствует $metadata Информация. Разве OData просто не пишет INNER JOIN в базе данных? Я что-то здесь упускаю? ... Похоже, это связано с тем, как я добавил Company ссылка и EF не нравится это. Похоже, я должен добавить его только в модель OData...

Похоже, что не имеет значения (по крайней мере, не с настройками, которые у меня есть), если я изменю AccountViewCompanyId в CompanyID чтобы соответствовать корпусу определения в Company Таблица.

я задал еще один связанный с этим вопрос: как добавить сложные свойства в модель, созданную с помощью ODataConventionModelBuilder, из модели EF.

2 ответа

Решение

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

Что касается исключения, то оно действительно было вызвано попаданием запроса в базу данных и попыткой получить столбцы, которые там не определены. Для дополнительной информации:

Что касается кода и ответа, другой вопрос - Расширение свойств навигации с помощью ODataQueryOptions. Код немного дополнен здесь следующим образом

public async Task<IHttpActionResult> Get(ODataQueryOptions<T> options)
{
    IQueryable<T> tempQuery = Context.AccountView;
    IQueryable<T result = tempQuery;

    if(options.SelectExpand != null)
    {
        if(options.Filter != null)
        {
            tempQuery = options.Filter.ApplyTo(tempQuery, new ODataQuerySettings()) as IQueryable<T>;   
        }
         /* Other options that should go to the DB level... */

        //The results need to be materialized, or otherwise EF throws a
        //NotSupportedException due to columns and properties that aren't in the DB...
        result = (await tempQuery.ToListAsync()).AsQueryable();

        //Do here the queries that can't be hit straight by the queries. E.g.
        //navigation properties not defined in EF views (that can't be augmented)...
        //E.g. get the company here.

        //This is needed to that OData formatter knows to render navigation links too.
        Request.ODataProperties().SelectExpandClause = options.SelectExpand.SelectExpandClause;
}

return Ok(result);

Попробуйте $ развернуть вместо & развернуть.

Другие вопросы по тегам