大纲 支持的接口 主要模型设计 控制器设计 数据源 查询(GET) 查询基类类型Entity的基础类型属性的值 查询派生类型Entity的基础类型属性值 查询基类类型Entity的派生类型属性值 查询派生类型Entity的派生类型属性值 完整更新(PUT) 完整更新基类类型Entity的基础类型属性值 完整更新派生类型Entity的基础类型属性值 完整更新基类类型Entity的派生类型属性值 完整更新派生类型Entity的派生类型属性值 新增(POST) 局部更新(PATCH) 局部更新基类类型Entity的单值基础类型属性 局部更新派生类型Entity的单值基础类型属性 局部更新基类类型Entity的单值派生类型属性 局部更新派生类型Entity的单值派生类型属性 删除(DELETE) 删除基类类型Entity的非空属性 删除派生类型Entity的非空属性 主程序 代码地址 参考资料
单值属性(Single-Valued Property)指的是实体中类型为复杂对象但非集合的属性,比如客户的账单地址(BillingAddress)、企业客户的注册地址等。 单值属性的操作涵盖了查询、替换、部分更新等多种场景。本文将结合代码示例,讲解如何在 OData 控制器中优雅地处理单值属性,并分析其在数据建模和接口设计中的重要性。
支持的接口
Request Method Route Template 说明 GET ~/{entityset}/{key}/{property} 查询基类类型Entity的基础类型属性值 GET ~/{entityset}/{key}/{cast}/{property} 查询派生类型Entity的基础类型属性值 GET ~/{entityset}/{key}/{property}/{cast} 查询基类类型Entity的派生类型属性值 GET ~/{entityset}/{key}/{cast}/{property}/{cast} 查询派生类型Entity的派生类型属性值 GET ~/{singleton}/{property} 查询基类类型单例的基础类型属性值 GET ~/{singleton}/{cast}/{property} 查询派生类型单例的基础类型属性值 GET ~/{singleton}/{property}/{cast} 查询基类类型单例的派生类型属性值 GET ~/{singleton}/{cast}/{property}/{cast} 查询派生类型单例的派生类型属性值 PATCH ~/{entityset}/{key}/{singlevaluedproperty} 局部更新基类类型Entity的单值基础类型属性 PATCH ~/{entityset}/{key}/{cast}/{singlevaluedproperty} 局部更新派生类型Entity的单值基础类型属性 PATCH ~/{entityset}/{key}/{singlevaluedproperty}/{cast} 局部更新基类类型Entity的单值派生类型属性 PATCH ~/{entityset}/{key}/{cast}/{singlevaluedproperty}/{cast} 局部更新派生类型Entity的单值派生类型属性 PATCH ~/{singleton}/{singlevaluedproperty} 局部更新基类类型Entity的单值基础类型属性 PATCH ~/{singleton}/{cast}/{singlevaluedproperty} 局部更新派生类型Entity的单值基础类型属性 PATCH ~/{singleton}/{singlevaluedproperty}/{cast} 局部更新基类类型Entity的单值派生类型属性 PATCH ~/{singleton}/{cast}/{singlevaluedproperty}/{cast} 局部更新派生类型Entity的单值派生类型属性 DELETE ~/{entityset}/{key}/{nullableproperty} 删除基类类型Entity的非空属性 DELETE ~/{entityset}/{key}/{cast}/{nullableproperty} 删除派生类型Entity的非空属性 DELETE ~/{singleton}/{nullableproperty} 删除基类类型单例的非空属性 DELETE ~/{singleton}/{cast}/{nullableproperty} 删除派生类型单例的非空属性
主要模型设计
在项目下新增Models文件夹,并添加Address、PostalAddress 、Customer和EnterpriseCustomer类。
namespace Lesson8. Models
{ public class Address { public required string Street { get ; set ; } }
}
namespace Lesson8. Models
{ public class PostalAddress : Address { public required string PostalCode { get ; set ; } }
}
using System. Net ; namespace Lesson8. Models
{ using System. Collections. Generic ; public class Customer { public int Id { get ; set ; } public string ? Name { get ; set ; } public Address? BillingAddress { get ; set ; } public List< string > ContactPhones { get ; set ; } = [ ] ; }
}
using System. Net ; namespace Lesson8. Models
{ using System. Collections. Generic ; public class EnterpriseCustomer : Customer { public decimal ? CreditLimit { get ; set ; } public Address? RegisteredAddress { get ; set ; } public List< Address> ShippingAddresses { get ; set ; } = new List< Address> ( ) ; }
}
控制器设计
在项目中新增Controller文件夹,然后添加CompanyController类。该类注册于ODataController,以便拥有如下能力:
OData 路由支持 继承 ODataController 后,控制器自动支持 OData 路由(如 /odata/Shapes(1)),可以直接响应 OData 标准的 URL 路径和操作。 OData 查询参数支持 可以使用 [EnableQuery] 特性,自动支持 $filter、$select、$orderby、$expand 等 OData 查询参数,无需手动解析。 OData 响应格式 返回的数据会自动序列化为 OData 标准格式(如 JSON OData),方便前端或其他系统消费。 OData Delta 支持 支持 Delta<T>、DeltaSet<T> 等类型,便于实现 PATCH、批量 PATCH 等 OData 特有的部分更新操作。 更丰富的 OData 语义 继承后可方便实现实体集、实体、导航属性、复杂类型等 OData 语义,提升 API 的表达能力。
using Lesson8. Models ;
using Microsoft. AspNetCore. Mvc ;
using Microsoft. AspNetCore. OData. Deltas ;
using Microsoft. AspNetCore. OData. Query ;
using Microsoft. AspNetCore. OData. Routing. Controllers ; namespace Lesson8. Controllers
{ public class CustomersController : ODataController { }
}
下面我们在该类中填充逻辑。
数据源
private static List< Customer> customers = new List< Customer> { new Customer { Id = 1 , Name = "Customer 1" , ContactPhones = new List< string > { "761-116-1865" } , BillingAddress = new Address { Street = "Street 1A" } } , new Customer { Id = 2 , Name = "Customer 2" , ContactPhones = new List< string > { "835-791-8257" } , BillingAddress = new PostalAddress { Street = "2A" , PostalCode = "14030" } } , new EnterpriseCustomer { Id = 3 , Name = "Customer 3" , ContactPhones = new List< string > { "157-575-6005" } , BillingAddress = new Address { Street = "Street 3A" } , CreditLimit = 4200 , RegisteredAddress = new Address { Street = "Street 3B" } , ShippingAddresses = new List< Address> { new Address { Street = "Street 3C" } } } , new EnterpriseCustomer { Id = 4 , Name = "Customer 4" , ContactPhones = new List< string > { "724-096-6719" } , BillingAddress = new Address { Street = "Street 4A" } , CreditLimit = 3700 , RegisteredAddress = new PostalAddress { Street = "Street 4B" , PostalCode = "22109" } , ShippingAddresses = new List< Address> { new Address { Street = "Street 4C" } } } } ;
查询(GET)
Request Method Route Template 说明 GET ~/{entityset}/{key}/{property} 查询基类类型Entity的基础类型属性值 GET ~/{entityset}/{key}/{cast}/{property} 查询派生类型Entity的基础类型属性值 GET ~/{entityset}/{key}/{property}/{cast} 查询基类类型Entity的派生类型属性值 GET ~/{entityset}/{key}/{cast}/{property}/{cast} 查询派生类型Entity的派生类型属性值 GET ~/{singleton}/{property} 查询基类类型单例的基础类型属性值 GET ~/{singleton}/{cast}/{property} 查询派生类型单例的基础类型属性值 GET ~/{singleton}/{property}/{cast} 查询基类类型单例的派生类型属性值 GET ~/{singleton}/{cast}/{property}/{cast} 查询派生类型单例的派生类型属性值
查询基类类型Entity的基础类型属性的值
Request Method Route Template 说明 GET ~/{entityset}/{key}/{primitiveproperty}/$value 查询基类类型Entity的基础类型属性的值 GET ~/{singleton}/{property} 查询基类类型单例的基础类型属性值
public ActionResult< Address> GetBillingAddress ( [ FromRoute ] int key) { var customer = customers. SingleOrDefault ( d => d. Id. Equals ( key) ) ; if ( customer == null || customer. BillingAddress == null ) { return NotFound ( ) ; } return customer. BillingAddress; }
curl --location 'http://localhost:5119/odata/Customers(2)/BillingAddress'
{ "@odata.context" : "http://localhost:5119/odata/$metadata#Customers(2)/BillingAddress" , "Street" : "2A" , "PostalCode" : "14030"
}
查询派生类型Entity的基础类型属性值
Request Method Route Template 说明 GET ~/{entityset}/{key}/{cast}/{property} 查询派生类型Entity的基础类型属性值 GET ~/{singleton}/{cast}/{property} 查询派生类型单例的基础类型属性值
public ActionResult< Address> GetRegisteredAddressFromEnterpriseCustomer ( [ FromRoute ] int key) { var enterpriseCustomer = customers. OfType < EnterpriseCustomer> ( ) . SingleOrDefault ( d => d. Id. Equals ( key) ) ; if ( enterpriseCustomer == null || enterpriseCustomer. RegisteredAddress == null ) { return NotFound ( ) ; } return enterpriseCustomer. RegisteredAddress; }
curl --location 'http://localhost:5119/odata/Customers(4)/Lesson8.Models.EnterpriseCustomer/RegisteredAddress'
{ "@odata.context" : "http://localhost:5119/odata/$metadata#Customers(4)/Lesson8.Models.EnterpriseCustomer/RegisteredAddress" , "Street" : "Street 4B" , "PostalCode" : "22109"
}
查询基类类型Entity的派生类型属性值
Request Method Route Template 说明 GET ~/{entityset}/{key}/{property}/{cast} 查询基类类型Entity的派生类型属性值 GET ~/{singleton}/{property}/{cast} 查询基类类型单例的派生类型属性值
public ActionResult< PostalAddress> GetBillingAddressOfPostalAddress ( [ FromRoute ] int key) { var customer = customers. SingleOrDefault ( d => d. Id. Equals ( key) ) ; if ( ! ( customer?. BillingAddress is PostalAddress billingAddress) ) { return NotFound ( ) ; } return billingAddress; }
curl --location 'http://localhost:5119/odata/Customers(2)/BillingAddress/Lesson8.Models.PostalAddress'
{ "@odata.context" : "http://localhost:5119/odata/$metadata#Customers(2)/BillingAddress/Lesson8.Models.PostalAddress" , "Street" : "2A" , "PostalCode" : "14030"
}
查询派生类型Entity的派生类型属性值
Request Method Route Template 说明 GET ~/{entityset}/{key}/{cast}/{property}/{cast} 查询派生类型Entity的派生类型属性值 GET ~/{singleton}/{cast}/{property}/{cast} 查询派生类型单例的派生类型属性值
public ActionResult< PostalAddress> GetRegisteredAddressOfPostalAddressFromEnterpriseCustomer ( [ FromRoute ] int key) { var enterpriseCustomer = customers. OfType < EnterpriseCustomer> ( ) . SingleOrDefault ( d => d. Id. Equals ( key) ) ; if ( ! ( enterpriseCustomer?. RegisteredAddress is PostalAddress registeredAddress) ) { return NotFound ( ) ; } return registeredAddress; }
curl --location 'http://localhost:5119/odata/Customers(4)/Lesson8.Models.EnterpriseCustomer/RegisteredAddress/Lesson8.Models.PostalAddress'
{ "@odata.context" : "http://localhost:5119/odata/$metadata#Customers(4)/Lesson8.Models.EnterpriseCustomer/RegisteredAddress/Lesson8.Models.PostalAddress" , "Street" : "Street 4B" , "PostalCode" : "22109"
}
完整更新(PUT)
Request Method Route Template 说明 PUT ~/{entityset}/{key}/{property} 完整更新基类类型Entity的基础类型属性值 PUT ~/{entityset}/{key}/{cast}/{property} PUT ~/{entityset}/{key}/{property}/{cast} PUT ~/{entityset}/{key}/{cast}/{property}/{cast} PUT ~/{singleton}/{property} PUT ~/{singleton}/{cast}/{property} PUT ~/{singleton}/{property}/{cast} PUT ~/{singleton}/{cast}/{property}/{cast}
完整更新基类类型Entity的基础类型属性值
Request Method Route Template 说明 PUT ~/{entityset}/{key}/{property} 完整更新基类类型Entity的基础类型属性值 PUT ~/{singleton}/{property}
public ActionResult PutToBillingAddress ( [ FromRoute ] int key, [ FromBody ] Address address) { var customer = customers. SingleOrDefault ( d => d. Id. Equals ( key) ) ; if ( customer == null ) { return NotFound ( ) ; } customer. BillingAddress = address; return Ok ( ) ; }
curl --location --request PUT 'http://localhost:5119/odata/Customers(1)/BillingAddress' \
--header 'Content-Type: application/json' \
--data '{"Street": "One Microsoft Way"
}'
完整更新派生类型Entity的基础类型属性值
Request Method Route Template 说明 PUT ~/{entityset}/{key}/{cast}/{property} PUT ~/{singleton}/{cast}/{property}
public ActionResult PutToRegisteredAddressFromEnterpriseCustomer ( [ FromRoute ] int key, [ FromBody ] Address address) { var enterpriseCustomer = customers. OfType < EnterpriseCustomer> ( ) . SingleOrDefault ( d => d. Id. Equals ( key) ) ; if ( enterpriseCustomer == null ) { return NotFound ( ) ; } enterpriseCustomer. RegisteredAddress = address; return Ok ( ) ; }
curl --location --request PUT 'http://localhost:5119/odata/Customers(3)/Lesson8.Models.EnterpriseCustomer/RegisteredAddress' \
--header 'Content-Type: application/json' \
--data '{"Street": "One Microsoft Way"
}'
完整更新基类类型Entity的派生类型属性值
Request Method Route Template 说明 PUT ~/{entityset}/{key}/{property}/{cast} PUT ~/{singleton}/{property}/{cast}
public ActionResult PutToBillingAddressOfPostalAddress ( [ FromRoute ] int key, [ FromBody ] PostalAddress billingAddress) { var customer = customers. SingleOrDefault ( d => d. Id. Equals ( key) ) ; if ( ! ( customer?. BillingAddress is PostalAddress billingAddressInstance) ) { return NotFound ( ) ; } billingAddressInstance. Street = billingAddress. Street; billingAddressInstance. PostalCode = billingAddress. PostalCode; return Ok ( ) ; }
curl --location --request PUT 'http://localhost:5119/odata/Customers(2)/BillingAddress/Lesson8.Models.PostalAddress' \
--header 'Content-Type: application/json' \
--data '{"Street": "2A","PostalCode": "14030"
}'
完整更新派生类型Entity的派生类型属性值
Request Method Route Template 说明 PUT ~/{entityset}/{key}/{cast}/{property}/{cast} PUT ~/{singleton}/{cast}/{property}/{cast}
public ActionResult PutToRegisteredAddressOfPostalAddressFromEnterpriseCustomer ( [ FromRoute ] int key, [ FromBody ] PostalAddress registeredAddress) { var enterpriseCustomer = customers. OfType < EnterpriseCustomer> ( ) . SingleOrDefault ( d => d. Id. Equals ( key) ) ; if ( enterpriseCustomer == null ) { return NotFound ( ) ; } enterpriseCustomer. RegisteredAddress = registeredAddress; return Ok ( ) ; }
curl --location --request PUT 'http://localhost:5119/odata/Customers(4)/Lesson8.Models.EnterpriseCustomer/RegisteredAddress/Lesson8.Models.PostalAddress' \
--header 'Content-Type: text/plain' \
--data '{"Street": "One Microsoft Way","PostalCode": "98052"
}'
新增(POST)
单值属性(如 BillingAddress、RegisteredAddress)只是实体的一个字段,不是集合,不能单独“新增”,所以不支持POST指令。
局部更新(PATCH)
Request Method Route Template 说明 PATCH ~/{entityset}/{key}/{singlevaluedproperty} 局部更新基类类型Entity的单值基础类型属性 PATCH ~/{entityset}/{key}/{cast}/{singlevaluedproperty} 局部更新派生类型Entity的单值基础类型属性 PATCH ~/{entityset}/{key}/{singlevaluedproperty}/{cast} 局部更新基类类型Entity的单值派生类型属性 PATCH ~/{entityset}/{key}/{cast}/{singlevaluedproperty}/{cast} 局部更新派生类型Entity的单值派生类型属性 PATCH ~/{singleton}/{singlevaluedproperty} 局部更新基类类型Entity的单值基础类型属性 PATCH ~/{singleton}/{cast}/{singlevaluedproperty} 局部更新派生类型Entity的单值基础类型属性 PATCH ~/{singleton}/{singlevaluedproperty}/{cast} 局部更新基类类型Entity的单值派生类型属性 PATCH ~/{singleton}/{cast}/{singlevaluedproperty}/{cast} 局部更新派生类型Entity的单值派生类型属性
局部更新基类类型Entity的单值基础类型属性
Request Method Route Template 说明 PATCH ~/{entityset}/{key}/{singlevaluedproperty} 局部更新基类类型Entity的单值基础类型属性 PATCH ~/{singleton}/{singlevaluedproperty} 局部更新基类类型Entity的单值基础类型属性
public ActionResult PatchToBillingAddress ( [ FromRoute ] int key, [ FromBody ] Delta< Address> delta) { var customer = customers. SingleOrDefault ( d => d. Id. Equals ( key) ) ; if ( customer == null ) { return NotFound ( ) ; } if ( customer. BillingAddress == null ) { return BadRequest ( "BillingAddress cannot be null." ) ; } if ( delta == null ) { return BadRequest ( "Invalid request body." ) ; } delta. Patch ( customer. BillingAddress) ; return Ok ( ) ; }
curl --location --request PATCH 'http://localhost:5119/odata/Customers(1)/BillingAddress' \
--header 'Content-Type: application/json' \
--data '{"Street": "One Microsoft Way"
}'
局部更新派生类型Entity的单值基础类型属性
Request Method Route Template 说明 PATCH ~/{entityset}/{key}/{cast}/{singlevaluedproperty} 局部更新派生类型Entity的单值基础类型属性 PATCH ~/{singleton}/{cast}/{singlevaluedproperty} 局部更新派生类型Entity的单值基础类型属性
public ActionResult PatchToRegisteredAddressFromEnterpriseCustomer ( [ FromRoute ] int key, [ FromBody ] Delta< Address> delta) { var enterpriseCustomer = customers. OfType < EnterpriseCustomer> ( ) . SingleOrDefault ( d => d. Id. Equals ( key) ) ; if ( enterpriseCustomer == null ) { return NotFound ( ) ; } if ( enterpriseCustomer. RegisteredAddress == null ) { return BadRequest ( "RegisteredAddress cannot be null." ) ; } if ( delta == null ) { return BadRequest ( "Invalid request body." ) ; } delta. Patch ( enterpriseCustomer. RegisteredAddress) ; return Ok ( ) ; }
curl --location --request PATCH 'http://localhost:5119/odata/Customers(3)/Lesson8.Models.EnterpriseCustomer/RegisteredAddress' \
--header 'Content-Type: application/json' \
--data '{"Street": "One Microsoft Way"
}'
局部更新基类类型Entity的单值派生类型属性
Request Method Route Template 说明 PATCH ~/{entityset}/{key}/{singlevaluedproperty}/{cast} 局部更新基类类型Entity的单值派生类型属性 PATCH ~/{singleton}/{singlevaluedproperty}/{cast} 局部更新基类类型Entity的单值派生类型属性
public ActionResult PatchToBillingAddressOfPostalAddress ( [ FromRoute ] int key, [ FromBody ] Delta< PostalAddress> delta) { var customer = customers. SingleOrDefault ( d => d. Id. Equals ( key) ) ; if ( ! ( customer?. BillingAddress is PostalAddress billingAddress) ) { return NotFound ( ) ; } delta. Patch ( billingAddress) ; return Ok ( ) ; }
curl --location --request PATCH 'http://localhost:5119/odata/Customers(2)/BillingAddress/Lesson8.Models.PostalAddress' \
--header 'Content-Type: application/json' \
--data '{"Street": "2A","PostalCode": "14030"
}'
局部更新派生类型Entity的单值派生类型属性
Request Method Route Template 说明 PATCH ~/{entityset}/{key}/{cast}/{singlevaluedproperty}/{cast} 局部更新派生类型Entity的单值派生类型属性 PATCH ~/{singleton}/{cast}/{singlevaluedproperty}/{cast} 局部更新派生类型Entity的单值派生类型属性
public ActionResult PatchToRegisteredAddressOfPostalAddressFromEnterpriseCustomer ( [ FromRoute ] int key, [ FromBody ] Delta< PostalAddress> delta) { var enterpriseCustomer = customers. OfType < EnterpriseCustomer> ( ) . SingleOrDefault ( d => d. Id. Equals ( key) ) ; if ( ! ( enterpriseCustomer?. RegisteredAddress is PostalAddress registeredAddress) ) { return NotFound ( ) ; } delta. Patch ( registeredAddress) ; return Ok ( ) ; }
curl --location --request PATCH 'http://localhost:5119/odata/Customers(4)/Lesson8.Models.EnterpriseCustomer/RegisteredAddress/Lesson8.Models.PostalAddress' \
--header 'Content-Type: application/json' \
--data '{"Street": "One Microsoft Way","PostalCode": "98052"
}'
删除(DELETE)
Request Method Route Template 说明 DELETE ~/{entityset}/{key}/{nullableproperty} 删除基类类型Entity的非空属性 DELETE ~/{entityset}/{key}/{cast}/{nullableproperty} 删除派生类型Entity的非空属性 DELETE ~/{singleton}/{nullableproperty} 删除基类类型单例的非空属性 DELETE ~/{singleton}/{cast}/{nullableproperty} 删除派生类型单例的非空属性
删除操作只能支持可以为null的属性,所以我们在模型定义时需要将要操作的属性(如本例中的Customer.BillingAddress和EnterpriseCustomer.RegisteredAddress )设置为nullable,否则请求无法路由到相关函数。
using System. Net ; namespace Lesson8. Models
{ using System. Collections. Generic ; public class Customer { public int Id { get ; set ; } public string ? Name { get ; set ; } public Address? BillingAddress { get ; set ; } public List< string > ContactPhones { get ; set ; } = [ ] ; }
}
using System. Net ; namespace Lesson8. Models
{ using System. Collections. Generic ; public class EnterpriseCustomer : Customer { public decimal ? CreditLimit { get ; set ; } public Address? RegisteredAddress { get ; set ; } public List< Address> ShippingAddresses { get ; set ; } = new List< Address> ( ) ; }
}
删除基类类型Entity的非空属性
public ActionResult DeleteToBillingAddress ( [ FromRoute ] int key) { var customer = customers. SingleOrDefault ( d => d. Id. Equals ( key) ) ; if ( customer == null ) { return NotFound ( ) ; } customer. BillingAddress = null ! ; return NoContent ( ) ; }
curl --location --request DELETE 'http://localhost:5119/odata/Customers(1)/BillingAddress'
删除派生类型Entity的非空属性
public ActionResult DeleteToRegisteredAddressFromEnterpriseCustomer ( [ FromRoute ] int key) { var enterpriseCustomer = customers. OfType < EnterpriseCustomer> ( ) . SingleOrDefault ( d => d. Id. Equals ( key) ) ; if ( enterpriseCustomer == null ) { return NotFound ( ) ; } enterpriseCustomer. RegisteredAddress = null ! ; return NoContent ( ) ; }
curl --location --request DELETE 'http://localhost:5119/odata/Customers(3)/Lesson8.Models.EnterpriseCustomer/RegisteredAddress'
主程序
using Lesson8. Models ;
using Microsoft. AspNetCore. OData ;
using Microsoft. OData. ModelBuilder ;
using Microsoft. OData. Edm ; var builder = WebApplication. CreateBuilder ( args) ; static IEdmModel GetEdmModel ( )
{ var modelBuilder = new ODataConventionModelBuilder ( ) ; modelBuilder. EntitySet < Customer> ( "Customers" ) ; return modelBuilder. GetEdmModel ( ) ;
} builder. Services. AddControllers ( ) . AddOData ( options => options. Select ( ) . Filter ( ) . OrderBy ( ) . Expand ( ) . Count ( ) . SetMaxTop ( null ) . AddRouteComponents ( "odata" , GetEdmModel ( ) )
) ; var app = builder. Build ( ) ; app. UseRouting ( ) ; app. MapControllers ( ) ; app. Run ( ) ;
服务文档
curl --location 'http://localhost:5119/odata'
{ "@odata.context" : "http://localhost:5119/odata/$metadata" , "value" : [ { "name" : "Customers" , "kind" : "EntitySet" , "url" : "Customers" } ]
}
模型元文档
curl --location 'http://localhost:5119/odata/$metadata'
< ? xml version= "1.0" encoding= "utf-8" ? >
< edmx: Edmx Version= "4.0" xmlns : edmx= "http://docs.oasis-open.org/odata/ns/edmx" > < edmx: DataServices> < Schema Namespace= "Lesson8.Models" xmlns= "http://docs.oasis-open.org/odata/ns/edm" > < EntityType Name= "Customer" > < Key> < PropertyRef Name= "Id" / > < / Key> < Property Name= "Id" Type= "Edm.Int32" Nullable= "false" / > < Property Name= "Name" Type= "Edm.String" Nullable= "false" / > < Property Name= "BillingAddress" Type= "Lesson8.Models.Address" Nullable= "false" / > < Property Name= "ContactPhones" Type= "Collection(Edm.String)" / > < / EntityType> < ComplexType Name= "Address" > < Property Name= "Street" Type= "Edm.String" Nullable= "false" / > < / ComplexType> < ComplexType Name= "PostalAddress" BaseType= "Lesson8.Models.Address" > < Property Name= "PostalCode" Type= "Edm.String" Nullable= "false" / > < / ComplexType> < EntityType Name= "EnterpriseCustomer" BaseType= "Lesson8.Models.Customer" > < Property Name= "CreditLimit" Type= "Edm.Decimal" Nullable= "false" Scale= "variable" / > < Property Name= "RegisteredAddress" Type= "Lesson8.Models.Address" Nullable= "false" / > < Property Name= "ShippingAddresses" Type= "Collection(Lesson8.Models.Address)" / > < / EntityType> < / Schema> < Schema Namespace= "Default" xmlns= "http://docs.oasis-open.org/odata/ns/edm" > < EntityContainer Name= "Container" > < EntitySet Name= "Customers" EntityType= "Lesson8.Models.Customer" / > < / EntityContainer> < / Schema> < / edmx: DataServices>
< / edmx: Edmx>
代码地址
https://github.com/f304646673/odata/tree/main/csharp/Lesson/Lesson8
参考资料
https://learn.microsoft.com/en-us/odata/webapi-8/fundamentals/property-routing?tabs=net60%2Cvisual-studio