2015-11-14 8 views
5

Attualmente mi sto insegnando OData ma mi sono imbattuto in una situazione che non sono stato in grado di risolvere. O sono io a fraintendere le specifiche OData o devo fare qualcosa per farlo funzionare.OData su API Web: come interrogare le proprietà annidate?

Ho creato un piccolo modello di entità Libri e Autori (EF/CF). Roba abbastanza semplice, con una relazione uno-a-molti da autori di libri:

modelBuilder.Entity<Book>().HasRequired(b => b.Author); 
modelBuilder.Entity<Author>().HasMany(a => a.Books); 

Ora, quando si interroga Autori vorrei essere in grado di espandere la proprietà libri e filtro sui suoi (annidati) proprietà. Ad esempio, se chiedo "che ha scritto i libri di Harry Potter", in questo modo ...

http://myBooksDatabase/Authors?$expand=Books&$filter=contains(Books/Name,'Harry Potter')&$select=Name 

... ottengo questo errore di risposta:

{ 
    error: { 
    code: "" 
    message: "The query specified in the URI is not valid. The parent value for a property access of a property 'Name' is not a single value. Property access can only be applied to a single value." 
    innererror: { 
     message: "The parent value for a property access of a property 'Name' is not a single value. Property access can only be applied to a single value." 
     type: "Microsoft.OData.Core.ODataException" 
     stacktrace: " at Microsoft.OData.Core.UriParser.Parsers.EndPathBinder.BindEndPath(EndPathToken endPathToken) at Microsoft.OData.Core.UriParser.Parsers.MetadataBinder.BindEndPath(EndPathToken endPathToken) at Microsoft.OData.Core.UriParser.Parsers.MetadataBinder.Bind(QueryToken token) at Microsoft.OData.Core.UriParser.Parsers.MetadataBinder.BindFunctionParameter(FunctionParameterToken token) at Microsoft.OData.Core.UriParser.Parsers.MetadataBinder.Bind(QueryToken token) at Microsoft.OData.Core.UriParser.Parsers.FunctionCallBinder.<BindFunctionCall>b__8(FunctionParameterToken ar) at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext() at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection) at Microsoft.OData.Core.UriParser.Parsers.FunctionCallBinder.BindFunctionCall(FunctionCallToken functionCallToken) at Microsoft.OData.Core.UriParser.Parsers.MetadataBinder.BindFunctionCall(FunctionCallToken functionCallToken) at Microsoft.OData.Core.UriParser.Parsers.MetadataBinder.Bind(QueryToken token) at Microsoft.OData.Core.UriParser.Parsers.FilterBinder.BindFilter(QueryToken filter) at Microsoft.OData.Core.UriParser.ODataQueryOptionParser.ParseFilterImplementation(String filter, ODataUriParserConfiguration configuration, IEdmType elementType, IEdmNavigationSource navigationSource) at Microsoft.OData.Core.UriParser.ODataQueryOptionParser.ParseFilter() at System.Web.OData.Query.FilterQueryOption.get_FilterClause() at System.Web.OData.Query.Validators.FilterQueryValidator.Validate(FilterQueryOption filterQueryOption, ODataValidationSettings settings) at System.Web.OData.Query.FilterQueryOption.Validate(ODataValidationSettings validationSettings) at System.Web.OData.Query.Validators.ODataQueryValidator.Validate(ODataQueryOptions options, ODataValidationSettings validationSettings) at System.Web.OData.Query.ODataQueryOptions.Validate(ODataValidationSettings validationSettings) at System.Web.OData.EnableQueryAttribute.ValidateQuery(HttpRequestMessage request, ODataQueryOptions queryOptions) at System.Web.OData.EnableQueryAttribute.ExecuteQuery(Object response, HttpRequestMessage request, HttpActionDescriptor actionDescriptor) at System.Web.OData.EnableQueryAttribute.OnActionExecuted(HttpActionExecutedContext actionExecutedContext)" 
     }- 
    }- 
} 

mi rendo conto che posso ottenere che da l'interrogazione dei libri entità ...

http://myBooksDatabase/Books?$expand=Author&$filter=contains(Name,'Harry') 

... ma il problema ricevo proviene da quando tento riferimento alla proprietà nidificate, non importa come lo faccio. La query sopra funziona, e presenta l'intera entità autore, ma se aggiungo &$select=Author/Name ottengo la seguente risposta:

{ 
    error: { 
    code: "" 
    message: "The query specified in the URI is not valid. Found a path with multiple navigation properties or a bad complex property path in a select clause. Please reword your query such that each level of select or expand only contains either TypeSegments or Properties." 
    innererror: { 
     message: "Found a path with multiple navigation properties or a bad complex property path in a select clause. Please reword your query such that each level of select or expand only contains either TypeSegments or Properties." 
     type: "Microsoft.OData.Core.ODataException" 
     stacktrace: " at Microsoft.OData.Core.UriParser.Visitors.SelectPropertyVisitor.ProcessTokenAsPath(NonSystemToken tokenIn) at Microsoft.OData.Core.UriParser.Visitors.SelectPropertyVisitor.Visit(NonSystemToken tokenIn) at Microsoft.OData.Core.UriParser.Syntactic.NonSystemToken.Accept(IPathSegmentTokenVisitor visitor) at Microsoft.OData.Core.UriParser.Parsers.SelectBinder.Bind(SelectToken tokenIn) at Microsoft.OData.Core.UriParser.Parsers.SelectExpandBinder.Bind(ExpandToken tokenIn) at Microsoft.OData.Core.UriParser.Parsers.SelectExpandSemanticBinder.Bind(IEdmStructuredType elementType, IEdmNavigationSource navigationSource, ExpandToken expandToken, SelectToken selectToken, ODataUriParserConfiguration configuration) at Microsoft.OData.Core.UriParser.ODataQueryOptionParser.ParseSelectAndExpandImplementation(String select, String expand, ODataUriParserConfiguration configuration, IEdmStructuredType elementType, IEdmNavigationSource navigationSource) at Microsoft.OData.Core.UriParser.ODataQueryOptionParser.ParseSelectAndExpand() at System.Web.OData.Query.Validators.SelectExpandQueryValidator.Validate(SelectExpandQueryOption selectExpandQueryOption, ODataValidationSettings validationSettings) at System.Web.OData.Query.SelectExpandQueryOption.Validate(ODataValidationSettings validationSettings) at System.Web.OData.Query.Validators.ODataQueryValidator.Validate(ODataQueryOptions options, ODataValidationSettings validationSettings) at System.Web.OData.Query.ODataQueryOptions.Validate(ODataValidationSettings validationSettings) at System.Web.OData.EnableQueryAttribute.ValidateQuery(HttpRequestMessage request, ODataQueryOptions queryOptions) at System.Web.OData.EnableQueryAttribute.ExecuteQuery(Object response, HttpRequestMessage request, HttpActionDescriptor actionDescriptor) at System.Web.OData.EnableQueryAttribute.OnActionExecuted(HttpActionExecutedContext actionExecutedContext)" 
     }- 
    }- 
} 

Ecco le mie due controller OData per autori e libri:

namespace My.OData.Controllers 
{ 
    public class AuthorsController : ODataController 
    { 
     // GET /Author 
     [EnableQuery] 
     public IQueryable<Author> Get() 
     { 
      return MediaContext.Singleton.Authors; 
     } 

     // GET /Authors(<key>) 
     [EnableQuery] 
     public SingleResult<Author> Get([FromODataUri] Guid key) 
     { 
      var result = MediaContext.Singleton.Authors.Where(b => b.Id == key); 
      return SingleResult.Create(result); 
     } 

     // GET /Authors(<key>)/Books 
     [EnableQuery] 
     public IQueryable<Book> GetBooks([FromODataUri] Guid key) 
     { 
      return MediaContext.Singleton.Authors.Where(a => a.Id == key).SelectMany(author => author.Books); 
     } 
    } 

    public class BooksController : ODataController 
    { 
     // GET /Books 
     [EnableQuery] 
     public IQueryable<Book> Get() 
     { 
      return MediaContext.Singleton.Books; 
     } 

     // GET /Books(<key>) 
     [EnableQuery] 
     public SingleResult<Book> Get([FromODataUri] Guid key) 
     { 
      var result = MediaContext.Singleton.Books.Where(b => b.Id == key); 
      return SingleResult.Create(result); 
     } 

     // GET /Books(<key>)/Author 
     [EnableQuery] 
     public SingleResult<Author> GetAuthor([FromODataUri] Guid key) 
     { 
      return SingleResult.Create(MediaContext.Singleton.Books.Where(b => b.Id == key).Select(b => b.Author)); 
     } 
    } 
} 

Così, come Ho detto, c'è qualcos'altro che devo aggiungere o configurare per far funzionare le proprietà dei riferimenti nelle entità correlate?

+2

Ok, un po 'di indagine e di lettura delle specifiche del OData v4 mi ha insegnato la sintassi del filtro dovrebbe essere usando la funzione "any", come questa: http: // myBooksDatabase/Authors? $ filter = Books/any (b: contains (b/Name, 'Harry Potter'))). Funziona, ma cosa succede se ho bisogno di * alcune * proprietà dei libri, come il titolo e il codice ISBN? Non riesco ancora a capire come specificare un'istruzione $ select che limiti le proprietà nidificate a quelle di cui ho bisogno. –

risposta

4

Jonas, spero che ci siete arrivati ​​alla fine :) Per tutti quegli scommettitori a casa, Jonas ha identificato 2 problemi:

  1. Come selezionare un'entità se almeno uno dei suoi elementi figlio soddisfa un criterio

  2. Come espandere entità figlio ma solo selezionare le colonne specifiche nel set esteso

Risposta 1: Usa 'qualsiasi' funzione per filtrare gli autori a coloro che hanno un libro che contiene la stringa 'Harry Potter' nel nome del libro

http://myBooksDatabase/Authors?$filter=Books/any(b:contains(b/Name,'Harry Potter'))&$select=Name 

REF: http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part2-url-conventions/odata-v4.0-errata03-os-part2-url-conventions-complete.html

5.1.1.10 Lambda operatori

OData definisce due operatori che valutano un'expr booleana essenza su una collezione. Entrambi devono essere preceduti da un percorso di navigazione che identifica una raccolta. L'argomento di un operatore lambda è un nome di variabile lambda seguito da due punti (:) e un'espressione booleana che utilizza il nome della variabile lambda per fare riferimento alle proprietà delle entità correlate identificate dal percorso di navigazione.

5.1.1.10.1 qualsiasi

Il qualsiasi operatore applica un'espressione booleana a ciascun membro di una collezione e restituisce true se l'espressione è vero per qualsiasi membro della collezione, altrimenti restituisce falso. L'operatore any senza argomento restituisce true se la raccolta non è vuota.

Esempio 79: tutti gli ordini che hanno tutti gli articoli con una quantità superiore a 100 http://host/service/Orders filtro $ = Voci/qualsiasi? (D: d/Quantità GT 100)

5.1.1.10.2 tutto

L'operatore all applica un'espressione booleana a ciascun membro di una raccolta e restituisce true se l'espressione è vera per tutti i membri della raccolta, altrimenti restituisce false. Esempio 80: tutti gli ordini che hanno solo gli oggetti con un quantitativo superiore a 100

http://host/service/Orders filtro $ = Voci/tutto? (D: d/Quantità GT 100)

Risposta 2: Utilizzare nidificato '$ selezionare' all'interno dei libri '$ espandere' dichiarazione di limitare le colonne che devono essere restituiti entro l'espansione

http://myBooksDatabase/Authors?$filter=Books/any(b:contains(b/Name,'Harry Potter'))&$select=Name&$expand=Books($select=Name,ISBN) 

funziona anche per la altro esempio offerto:

http://myBooksDatabase/Books?$expand=Author($select=Name)&$filter=contains(Name,'Harry')&$select=Name,ISBN 

Ma queste due domande non sono proprio la stessa cosa, la prima query troveranno gli autori che hanno scritto un libro con 'Harry Potter' nel nome, ma il $ espandere elencherà tutti i libri che l'autore ha scritto, anche se "Harry Potter" non è nel nome.

questo non è destinato ad essere un set di risultati completo, solo un esempio per illustrare il punto, si prega di notare Le Fiabe di Beda il Bardo non include la stringa Harry Potter nel nome, ma viene restituito perché l'autore ha scritto altri libri che contengono Harry Potter nel nome.

[ 
    { Name: "J K Rowling", Books: [ 
    { Name: "Harry Potter and the Philosopher's Stone", ISBN: "9781408855652" }, 
    { Name: "The Tales of Beedle the Bard", ISBN: "9780747599876" }, 
    { Name: "Harry Potter and the Cursed Child - Parts I and II", ISBN: "9780751565355" } 
    ] }, 
    { Name: "Bruce Foster", Books: [ 
    { Name: "Harry Potter: A Pop-Up Book: Based on the Film Phenomenon", ISBN: "9781608870080" } 
    ]} 
] 

La seconda query restituirà tutti i libri nel database con 'Harry Potter' nel nome, indipendentemente dell'autore, ma includerà il nome dell'autore:

[ 
    { Name: "Harry Potter and the Philosopher's Stone", ISBN: "9781408855652", Author: { Name: "J K Rowling" } }, 
    { Name: "Harry Potter and the Cursed Child - Parts I and II", ISBN: "9780751565355", Author: { Name: "J K Rowling" } }, 
    { Name: "Harry Potter: A Pop-Up Book: Based on the Film Phenomenon", ISBN: "9781608870080", Author: { Name: "Bruce Foster" } } 
] 

Così, mentre OP è possibile ottenere simile dati modificando il controller primario tra cui selezionare, i dati sono in una forma diversa e possono contenere inf ridondante/replicato ormation, o può filtrare le righe che altrimenti si sarebbero aspettati. Se cambi il controller, cambierai la forma risultante dei dati, quindi non prendere decisioni del genere in fretta.

notare che, mentre la specificazione OData v4 include una serie di opzioni diverse per l'utilizzo di $ selezionare e $ espandere, non tutte queste opzioni di sintassi sono supportati dall'implementazione API Web ASP.Net forniti sulla rete ufficiale Nuget pacchetti ...

non sono sicuro sul ragionamento ufficiale, ma finora non sono stati svantaggiati (quando si tratta di nidificato $ espandere e $ selezionare) da questa limitata implementazione delle specifiche

Gli esempi offerti da questa soluzione sono stati testati contro pacchetto Microsoft.AspNet.OData v5.6.0 - 5.9.1

+0

Grazie mille !!! –