ASP.NET Core OData 实践——Lesson8增删改查单值类型Property(C#)

article/2025/6/25 22:01:18

大纲

  • 支持的接口
  • 主要模型设计
  • 控制器设计
    • 数据源
    • 查询(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 MethodRoute 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,以便拥有如下能力:

  1. OData 路由支持
    继承 ODataController 后,控制器自动支持 OData 路由(如 /odata/Shapes(1)),可以直接响应 OData 标准的 URL 路径和操作。
  2. OData 查询参数支持
    可以使用 [EnableQuery] 特性,自动支持 $filter、$select、$orderby、$expand 等 OData 查询参数,无需手动解析。
  3. OData 响应格式
    返回的数据会自动序列化为 OData 标准格式(如 JSON OData),方便前端或其他系统消费。
  4. OData Delta 支持
    支持 Delta<T>、DeltaSet<T> 等类型,便于实现 PATCH、批量 PATCH 等 OData 特有的部分更新操作。
  5. 更丰富的 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 MethodRoute 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 MethodRoute 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;}
  • Request
curl --location 'http://localhost:5119/odata/Customers(2)/BillingAddress'
  • Response
{"@odata.context": "http://localhost:5119/odata/$metadata#Customers(2)/BillingAddress","Street": "2A","PostalCode": "14030"
}

查询派生类型Entity的基础类型属性值

Request MethodRoute 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;}
  • Request
curl --location 'http://localhost:5119/odata/Customers(4)/Lesson8.Models.EnterpriseCustomer/RegisteredAddress'
  • Response
{"@odata.context": "http://localhost:5119/odata/$metadata#Customers(4)/Lesson8.Models.EnterpriseCustomer/RegisteredAddress","Street": "Street 4B","PostalCode": "22109"
}

查询基类类型Entity的派生类型属性值

Request MethodRoute 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;}
  • Request
curl --location 'http://localhost:5119/odata/Customers(2)/BillingAddress/Lesson8.Models.PostalAddress'
  • Response
{"@odata.context": "http://localhost:5119/odata/$metadata#Customers(2)/BillingAddress/Lesson8.Models.PostalAddress","Street": "2A","PostalCode": "14030"
}

查询派生类型Entity的派生类型属性值

Request MethodRoute 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;}
  • Request
curl --location 'http://localhost:5119/odata/Customers(4)/Lesson8.Models.EnterpriseCustomer/RegisteredAddress/Lesson8.Models.PostalAddress'
  • Response
{"@odata.context": "http://localhost:5119/odata/$metadata#Customers(4)/Lesson8.Models.EnterpriseCustomer/RegisteredAddress/Lesson8.Models.PostalAddress","Street": "Street 4B","PostalCode": "22109"
}

完整更新(PUT)

Request MethodRoute 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 MethodRoute 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();}
  • Request
curl --location --request PUT 'http://localhost:5119/odata/Customers(1)/BillingAddress' \
--header 'Content-Type: application/json' \
--data '{"Street": "One Microsoft Way"
}'

完整更新派生类型Entity的基础类型属性值

Request MethodRoute 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();}
  • Request
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 MethodRoute 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();}
  • Request
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 MethodRoute 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();}
  • Request
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 MethodRoute 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 MethodRoute 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();}
  • Request
curl --location --request PATCH 'http://localhost:5119/odata/Customers(1)/BillingAddress' \
--header 'Content-Type: application/json' \
--data '{"Street": "One Microsoft Way"
}'

局部更新派生类型Entity的单值基础类型属性

Request MethodRoute 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();}
  • Request
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 MethodRoute 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();}
  • Request
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 MethodRoute 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();}
  • Request
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 MethodRoute 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!; // Use null-forgiving operator to suppress the warningreturn NoContent();}
  • Request
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!; // Use null-forgiving operator to suppress the warningreturn NoContent();}
  • Request
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();

服务文档

  • Request
curl --location 'http://localhost:5119/odata'
  • Response
{"@odata.context": "http://localhost:5119/odata/$metadata","value": [{"name": "Customers","kind": "EntitySet","url": "Customers"}]
}

模型元文档

  • Request
curl --location 'http://localhost:5119/odata/$metadata'
  • Response
<?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

http://www.hkcw.cn/article/BUySJtcqrx.shtml

相关文章

(LeetCode 每日一题)135. 分发糖果 ( 贪心 )

题目&#xff1a;135. 分发糖果 思路&#xff1a;贪心两遍循环&#xff0c;时间复杂度0(n)。 在满足所有人都有一个糖果的情况下&#xff0c;进行两遍循环 第一遍循环&#xff1a;从左到右&#xff0c;满足当ratings[i]>ratings[i-1]时&#xff0c;v[i]v[i-1]1 第二遍循环&a…

DAX权威指南6:DAX 高级概念(扩展表)、DAX 计算常见优化

文章目录 十四、 DAX 高级概念14.1 扩展表14.1.1 扩展表的定义14.1.2 表扩展与双向过滤14.1.3 筛选上下文传播14.1.4 RELATED 和 LOOKUPVALUE14.1.5 扩展表结构在表定义时就已经确定 14.2 表筛选和列筛选14.2.1 表筛选和列筛选14.2.1.1 DAX筛选机制 14.2.2 ALL函数的真实含义14…

selenium-自动更新谷歌浏览器驱动

1、简介 selenium最初是一个自动化测试工具&#xff0c;而爬虫中使用它主要是为了解决requests无法直接执行JavaScript代码的问题&#xff0c;因为有些网页数据是通过JavaScript动态加载的。selenium本质是通过驱动浏览器&#xff0c;完全模拟浏览器的操作&#xff0c;比如输入…

高效视频倍速播放插件推荐

软件介绍 本文介绍一款名为Global Speed的视频速度控制插件&#xff0c;该插件在插件市场评分极高&#xff0c;被公认为目前最好用的视频倍速插件之一。 插件安装与基本功能 安装Global Speed插件后&#xff0c;用户只需点击插件图标即可选择播放倍数&#xff0c;最高支持16…

60、Polly瞬态故障处理

Polly 是一个.NET 弹性和瞬态故障处理库&#xff0c;在分布式系统和微服务架构中&#xff0c;可有效处理网络不稳定、服务依赖故障或资源限制等引发的瞬态故障&#xff0c;保障系统的稳定性和可靠性&#xff0c;以下是其常见策略及应用说明&#xff1a; 核心策略 1.重试策略 …

vulnyx loweb writeup

信息收集 arp-scan nmap 这里也可以接其它选项进行深入的扫描&#xff0c;因为我是打完后再写的wp&#xff0c;第一次做的时候我是扫了的&#xff0c;但是我没有得到什么有用的信息&#xff0c;所以写wp的时候我就没放出来了。这里直接去web 获取userFlag 一个默认的apache2 …

学员匿名举报教练骚扰后怀疑店长泄密:这两个教练在找她!

学员匿名举报教练骚扰后怀疑店长泄密。小孙反映,她感觉被健身房的两位教练骚扰了,她给记者看了聊天记录,对方曾说“从你身上让我能感受到你无比强大的力量”,“果然是顶级白月光”。最近让她倍感困扰的是,店长把她匿名举报的事告诉了涉事教练,导致这两个教练在找她,她要…

距2025高考还有4天 家长准备了什么礼物?

距2025高考还有4天 家长准备了什么礼物?!今天已经是6月2号,距离高考只剩下4天。每年高考结束后,许多高三学生都迫不及待地想要收到家长为他们准备的礼物,比如手机或电脑。高中三年对学生来说确实很辛苦,无论是在身体上还是精神上都付出了很多。高考结束后,学生们确实需要…

深圳天气 六一“不下雨通知”逗乐众人

深圳天气 六一“不下雨通知”逗乐众人!上海入汛首日遭遇了持续整天的大雨。截至18时,累计降水量显示普降大雨,市区徐家汇站在下午三点前几乎未曾停歇。雨雾和低云导致垂直能见度极低,全市最高气温仅在18-20℃之间,许多市民穿起了长袖甚至羊毛衫,这种天气让全国网友感到惊…

男子迷路拒绝救援后又求助 自信误判险酿祸

5月31日端午节,在北京房山一处野山中,一名男子登山迷路。他给警方打电话询问下山道路,警方随后联系了房山蓝天救援队。晚上8点多,当救援队员询问男子详细信息时,男子表示不想麻烦救援队,称自己能找到路下山。尽管如此,为了安全起见,房山蓝天救援队还是启动了救援程序。…

印度一游客摸老虎自拍遭袭击 触摸禁忌区引攻击

泰国普吉岛知名观光景点“老虎王国”近日发生了一起惊险事件。一名印度游客在与老虎合影时,因触摸老虎遭到攻击,现场画面在社交媒体上引发了广泛关注。该景点以“一生仅有一次的与虎互动体验”为卖点,吸引了众多游客。事发时,这名游客手持链条与老虎并排站立,驯兽师正用棍…

李亚鹏宣布将幼儿园无偿移交 为社会做贡献

6月1日,知名演员李亚鹏现身北京培德书院幼儿园六一活动。在活动现场,他宣布将把培德书院幼儿园无偿移交给一位资深教育家管理。他表示,人来到这个世界上总要为社会做点什么,这与个人财富无关,而是个人的价值观。培德书院幼儿园由李亚鹏于2011年前后创办,定位高端民办教育…

男子为省30元钱不幸离世 高原缺氧悲剧引发关注

46岁的河南卡车司机常志荣近日在青藏线因高原缺氧离世。他的骨灰及车辆由多名爱心司机跨越2400多公里,从五道梁地区送回老家安阳林州。其中一位司机表示:“不让家属承担一切费用,中国人就该互相帮助”。青海五道梁地区海拔4665米,含氧量不足海平面50%。5月27日,常志荣在此…

Spring框架学习day6--事务管理

Spring事务管理 Spring事务管理是在AOP的基础上&#xff0c;当我们的方法完全执行成功后&#xff0c;再提交事务&#xff0c;如果方法中有异常&#xff0c;就不提交事务 Spring中的事务管理有两种方式&#xff1a; ​ 1.编程式事务 ​ 需要我们在业务代码中手动提交 ​ 2.声明式…

【C盘瘦身】Docker安装目录占用C盘过大,一键移动给C盘瘦身

文章目录 前言一、Docker移动3步骤1. 进入docker的设置页面2. 点击“Browse”&#xff0c;选择D盘新建的目标目录3. 点击“Apply & restart”&#xff0c;选择Yes4. 移动完毕 二、结果检查 前言 最近安装了Dify&#xff0c;由于是Docker安装&#xff0c;不想安装完后&…

百度golang研发一面面经

输入一个网址&#xff0c;到显示界面&#xff0c;中间的过程是怎样的 IP 报文段的结构是什么 Innodb 的底层结构 知道几种设计模式 工厂模式 简单工厂模式&#xff1a;根据传入类型参数判断创建哪种类型对象工厂方法模式&#xff1a;由子类决定实例化哪个类抽象工厂模式&#…

CTF:网络安全的实战演练场

文章目录 每日一句正能量前言一、CTF简介&#xff08;一&#xff09;什么是CTF&#xff1f;&#xff08;二&#xff09;CTF的历史 二、CTF比赛形式&#xff08;一&#xff09;线上赛&#xff08;Online CTF&#xff09;&#xff08;二&#xff09;线下赛&#xff08;Offline CT…

Javaweb学习——day1(JavaWeb 介绍与开发环境搭建)

文章目录 1. 什么是 JavaWeb&#xff1f;2. HTTP 协议与请求响应流程2.1 HTTP 概述&#xff1a;2.2 请求响应流程&#xff1a;2.3 常见 HTTP 方法&#xff1a; 3. 开发环境搭建3.1 安装 Tomcat3.1.1 我的是win11 64位版&#xff0c;选择下载如图&#xff1a;3.1.2 解压 Tomcat3…

【沉浸式求职学习day52】【初识Mybaits】

沉浸式求职学习 什么是Mybatis1.持久化2.持久层3.为什么需要Mybatis&#xff1f;4.第一个Mybatis程序 CRUD1、namespace2、select3、Insert4、update5、Delete6、万能Map7、模糊查询 配置解析1、核心配置文件2、环境配置&#xff08;environments&#xff09;3、属性&#xff0…

mapStruct实体类属性映射工具实现

mapStruct实体类属性映射实现 1. 概述对比 BeanUtils.copyProperties场景对比 快速入门lombok mapStructmaven 依赖代码实现1. 定义两个要转换的实体类2. 定义转化接口测试 mapStruct Spring1. 注册成bean需要使用bean 转化规则1. 成员变量名不同时2. 子对象映射3. 数据类型映…