2015-11-14 71 views
5

我目前在教導自己OData,但我遇到了一直無法解決的情況。要麼是我誤解OData規範,要麼我需要做一些事情才能使其發揮作用。OData over Web API - 如何查詢嵌套屬性?

我建立了書籍和作者實體(EF/CF)的小型模型。很簡單的東西從作者到書一到一對多的關係:

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

現在,查詢作者,當我想能夠擴大其(嵌套)性質的書籍屬性和過濾器。例如,如果我問:「是誰寫的哈利波特」,像這樣......

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

...我得到這個錯誤響應:

{ 
    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)" 
     }- 
    }- 
} 

我知道我可以從查詢圖書實體...

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

...但我得到的問題來自於當我嘗試引用嵌套的屬性時,不管我怎麼做。上面的查詢工作,並提出了整體作者實體,但如果我添加&$select=Author/Name我得到如下回應:

{ 
    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)" 
     }- 
    }- 
} 

這裏是我的兩個的OData控制器爲作者和書籍:

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)); 
     } 
    } 
} 

因此,像我說過,還有什麼我需要添加或配置,使相關實體中的引用屬性工作?

+2

好了,多了一些調查和V4的OData規範的閱讀教給我的過濾器語法應該是使用「any」函數,如下所示: http:// myBooksDatabase/Authors?$ filter = Books/any(b:contains(b/Name,'Harry Potter'))。這是有效的,但是如果我需要一些*屬性的書籍,比如標題和ISBN呢?我仍然無法弄清楚如何指定一個$ select語句來將嵌套屬性限制爲我需要的。 –

回答

4

喬納斯,我希望你到底:) 了那裏,在家裏所有的賭客,喬納斯已經確定了2個問題:

  1. 如何,如果它的至少一個的子項中選擇一個實體滿足準則

  2. 如何擴大孩子的實體而只在擴大選擇特定的列設置

答1: 使用「任意」功能,筆者篩選,找出誰有一本書,包含書中

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算

的OData定義兩個操作符評估布爾EXPR對集合的激情。兩者都必須預先標識集合的導航路徑。 lambda運算符的參數是一個lambda變量名稱,後跟一個冒號(:)和一個使用lambda變量名稱來引用由導航路徑標識的相關實體的屬性的布爾表達式。

5.1.1.10.1任何

的任何操作者施加一個布爾表達式的集合的每個成員,如果表達式的是收集的任何成員真正返回真,否則返回假。如果集合不爲空,則沒有參數的任何運算符將返回true。

實施例79:(:d /數量GT 100 d)

5.1.1.10.2所有

具有任何項目與數量大於100 http://host/service/Orders $濾波器=筆數/任何所有訂單

all運算符將一個布爾表達式應用於集合的每個成員,並且如果表達式對集合的所有成員都爲真,則返回true,否則返回false。 實施例80:(:d /數量GT 100 d)

答2: 僅具有項目與數量大於100

http://host/service/Orders $濾波器=項目/所有所有訂單使用嵌套的內部「$選擇」圖書「$擴大」語句,以限制應擴大

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

也適用於該內返回的列其他的例子提出:

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

但是這兩個查詢都不太一樣,第一個查詢就會發現誰的名義寫了一本書「哈利·波特」的作者,但$擴展將列出所有書籍作者寫道,即使「哈利波特」不在名稱中。

這並非是一個完整的結果集,只是一個例子來說明這一點,請注意Beedle的傳說吟遊詩人不包括字符串哈利波特的名字,但它因爲作者已經寫了其他書籍,名稱中確實有Harry Potter

[ 
    { 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" } 
    ]} 
] 

第二個查詢將返回所有的書在名與「哈利·波特」的數據庫,無論作者本人觀點,但將包括作者的名字:

[ 
    { 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" } } 
] 

因此,儘管OP建議你可以得到相似數據通過改變主控制器來選擇,數據的形狀不同,可能包含冗餘/複製的inf或者可能會過濾出您原本預期的行。如果你改變了控制器,你將會改變數據的合成形狀,所以不要在匆忙中做出這樣的決定。

注意的是,雖然OData的V4規範包括許多用於使用不同的選項$選擇$擴大,不是所有的這些語法選項是通過官方提供的ASP.Net的Web API實現支持的NuGet包...

我不知道官方理由,但到目前爲止,我還沒有被不利(當它涉及到嵌套$擴大$選擇)通過這有限的實施規範

在這個解決方案提供的例子已經對包Microsoft.AspNet.OData V5.6.0測試 - 5.9.1

+0

謝謝sooo多!!! –