在ASP.NET Web API中使用OData的Containment
通常情况下,一个OData的EDM(Entity Data Model)在配置的时候定义了,才可以被查询或执行各种操作。比如如下:
builder.EntitySet<SomeModel>("SomeModels");
可能会这样查询:http://localhost:8888/odata/SomeModels
如果SomeModel中有一个集合导航属性,该如何获取呢?比如:
public class SomeModel { public int Id{get;set;} public IList<AnotherModel> AnotherModels{get;set;} } public class AnotherModel {
我们是否可以直接在SomeModel中获取所有的AnotherModel, 而不是通过如下方式获取:
builder.EntitySet<AnotherModel>("AnotherModels"); http://localhost:8888/odata/AnotherModels
OData为我们提供了Containment,只要为某个集合导航属性加上[Contained]特性,就可以按如下方式获取某个EDM模型下的集合导航属性,比如:
http://localhost:8888/odata/SomeModels(1)/AnotherModels
好先定义模型。
public class Account { public int AccountID { get; set; } public string Name { get; set; } [Contained] public IList<PaymentInstrument> PayinPIs { get; set; } } public class PaymentInstrument { public int PaymentInstrumentID { get; set; } public string FriendlyName { get; set; } }
以上,一旦在PayinPIs这个集合导航属性上加上[Contained]特性,只要在controller中再提供获取集合导航属性的方法,我们就可以按如下方式,通过Account获取PaymentInstrument集合。如下:
http://localhost:8888/odata/Accounts(100)/PayinPIs
在WebApiConfig类中定义如下:
public static class WebApiConfig { public static void Register(HttpConfiguration config) { ... config.MapODataServiceRoute( routeName: "ODataRoute", routePrefix: "odata", model: GetModel()); } private static IEdmModel GetModel() { ODataConventionModelBuilder builder = new ODataConventionModelBuilder(); builder.EntityType<PaymentInstrument>(); builder.EntitySet<Account>("Accounts"); return builder.GetEdmModel(); } }
在API端定义如下:
public class AccountsController : ODataController { private static IList<Account> _accounts = null; public AccountsController() { if(_accounts==null) { _accounts = InitAccounts(); } } [EnableQuery] public IHttpActionResult Get() { return Ok(_accounts.AsQueryable()); } [EnableQuery] public IHttpActionResult GetById([FromODataUri] int key) { var account = _accounts.Single(a => a.AccountID == key); return Ok(account); } //获取Account所有的PaymentInstrument集合 [EnableQuery] public IHttpActionResult GetPayinPIs([FromODataUri]int key) { var payinPIs = _accounts.Single(a => a.AccountID == key).PayinPIs; return Ok(payinPIs); } private static IList<Account> InitAccounts() { var accounts = new List<Account>() { new Account() { AccountID = 100, Name="Name100", PayoutPI = new PaymentInstrument() { PaymentInstrumentID = 100, FriendlyName = "Payout PI: Paypal", }, PayinPIs = new List<PaymentInstrument>() { new PaymentInstrument() { PaymentInstrumentID = 101, FriendlyName = "101 first PI", }, new PaymentInstrument() { PaymentInstrumentID = 102, FriendlyName = "102 second PI", }, }, }, }; return accounts; } }
以上的GetPayinPIs方法可以让我们根据Account获取其集合导航属性PayinPIs。
好,现在PayinPIs加上了[Contained]特性,也配备了具体的Action,现在开始查询:
http://localhost:64696/odata/Accounts(100)/PayinPIs
能查询到所有的PaymentInstrument。
此时,PayinPIs集合导航属性在元数据中是如何呈现的呢?查询如下:
http://localhost:64696/odata/$metadata
相关部分为:
<EntityType Name="Account"> <Key> <PropertyRef Name="AccountID" /> </Key> <Property Name="AccountID" Type="Edm.Int32" Nullable="false" /> <Property Name="Name" Type="Edm.String" /> <NavigationProperty Name="PayinPIs" Type="Collection(MyODataContainmentSample.PaymentInstrument)" ContainsTarget="true" /> </EntityType>
如果把PayinPIs上的[Contained]特性去掉呢?去掉后再次查询如下:
http://localhost:64696/odata/Accounts(100)/PayinPIs
返回404 NOT FOUND
再来看去掉[Contained]特性后相关的元数据:
<NavigationProperty Name="PayinPIs" Type="Collection(MyODataContainmentSample.PaymentInstrument)" />
没去掉[Contained]特性之前是:
<NavigationProperty Name="PayinPIs" Type="Collection(MyODataContainmentSample.PaymentInstrument)" ContainsTarget="true" />
原来,在一个集合导航属性上添加[Contained]特性,实际是让ContainsTarget="true",而默认状况下,ContainsTarget="false"。
^_^