单值属性(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
参考资料