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

article/2025/6/19 6:01:05

大纲

  • 支持的接口
  • 主要模型设计
  • 控制器设计
    • 数据源
    • 查询(GET)
      • 查询基础类型的原始类型属性
        • 查询基类类型Entity的基础类型属性的值
        • 查询基类类型Entity的派生类型属性的原始值
      • 查询派生类型Entity的基础类型属性
        • 查询派生类型Entity的属性值
        • 查询派生类型Entity的派生类型属性的原始值
    • 新增(POST)
    • 完整更新(PUT)
      • 完整更新基类类型Entity的属性值
      • 完整更新派生类型Entity的属性值
    • 局部更新(PATCH)
    • 删除(DELETE)
      • 删除基类类型Entity的非空属性
      • 删除派生类型Entity的非空属性
  • 主程序
    • 服务文档
    • 模型元文档
  • 代码地址
  • 参考资料

原始属性(Primitive Property)是 OData 实体中最基础的数据类型属性,如字符串、数字、布尔值等。本文将带你了解如何通过 OData API 查询、更新这些原始属性,并探讨它们在实际业务中的常见用法和注意事项。

支持的接口

Request MethodRoute Template说明
GET~/{entityset}/{key}/{property}查询基类类型Entity的属性值
GET~/{entityset}/{key}/{cast}/{property}查询派生类型Entity的属性值
GET~/{entityset}/{key}/{property}/$value查询基类类型Entity的派生类型属性的原始值
GET~/{entityset}/{key}/{cast}/{property}/$value查询派生类型Entity的派生类型属性的原始值
GET~/{singleton}/{property}查询基类类型单例的属性值
GET~/{singleton}/{cast}/{property}查询派生类型单例的属性值
GET~/{singleton}/{property}/$value查询基类类型单例的派生类型属性的原始值
GET~/{singleton}/{cast}/{property}/$value查询派生类型单例的派生类型属性的原始值
PUT~/{entityset}/{key}/{property}完整更新基类类型Entity的属性值
PUT~/{entityset}/{key}/{cast}/{property}完整更新派生类型Entity的属性值
PUT~/{singleton}/{property}完整更新基类类型单例的属性值
PUT~/{singleton}/{cast}/{property}完整更新派生类型单例的属性值
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}/$value查询基类类型Entity的派生类型属性的原始值
GET~/{entityset}/{key}/{cast}/{property}/$value查询派生类型Entity的派生类型属性的原始值
GET~/{singleton}/{property}查询基类类型单例的属性值
GET~/{singleton}/{cast}/{property}查询派生类型单例的属性值
GET~/{singleton}/{property}/$value查询基类类型单例的派生类型属性的原始值
GET~/{singleton}/{cast}/{property}/$value查询派生类型单例的派生类型属性的原始值

查询基础类型的原始类型属性

Request MethodRoute Template说明
GET~/{entityset}/{key}/{property}查询基类类型Entity的属性值
GET~/{singleton}/{property}查询基类类型单例的属性值
        [EnableQuery]public ActionResult<string> GetName([FromRoute] int key){var customer = customers.SingleOrDefault(d => d.Id.Equals(key));if (customer == null || customer.Name == null){return NotFound();}return customer.Name;}
查询基类类型Entity的基础类型属性的值
Request MethodRoute Template说明
GET~/{entityset}/{key}/{property}/$value查询基类类型Entity的派生类型属性的原始值
GET~/{singleton}/{property}/$value查询基类类型单例的派生类型属性的原始值
  • Request
curl --location 'http://localhost:5119/odata/Customers(1)/Name'
  • Response
{"@odata.context": "http://localhost:5119/odata/$metadata#Customers(1)/Name","value": "Customer 1"
}
查询基类类型Entity的派生类型属性的原始值
  • Request
curl --location 'http://localhost:5119/odata/Customers(1)/Name/$value'
  • Response
Customer 1

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

Request MethodRoute Template说明
GET~/{entityset}/{key}/{cast}/{property}查询派生类型Entity的属性值
GET~/{entityset}/{key}/{cast}/{property}/$value查询派生类型Entity的派生类型属性的原始值
GET~/{singleton}/{cast}/{property}查询派生类型单例的属性值
GET~/{singleton}/{cast}/{property}/$value查询派生类型单例的派生类型属性的原始值
        [EnableQuery]public ActionResult<decimal> GetCreditLimitFromEnterpriseCustomer([FromRoute] int key){var enterpriseCustomer = customers.OfType<EnterpriseCustomer>().SingleOrDefault(d => d.Id.Equals(key));if (enterpriseCustomer == null || !enterpriseCustomer.CreditLimit.HasValue){return NotFound();}return enterpriseCustomer.CreditLimit.Value;}
查询派生类型Entity的属性值
Request MethodRoute Template说明
GET~/{entityset}/{key}/{cast}/{property}查询派生类型Entity的属性值
GET~/{singleton}/{cast}/{property}查询派生类型单例的属性值
        [EnableQuery]public ActionResult<decimal> GetCreditLimit([FromRoute] int key){var enterpriseCustomer = customers.OfType<EnterpriseCustomer>().SingleOrDefault(d => d.Id.Equals(key));if (enterpriseCustomer == null){return NotFound();}return enterpriseCustomer.CreditLimit;}
  • Request
curl --location 'http://localhost:5119/odata/Customers(3)/Lesson8.Models.EnterpriseCustomer/CreditLimit'
  • Response
{"@odata.context": "http://localhost:5119/odata/$metadata#Customers(3)/Lesson8.Models.EnterpriseCustomer/CreditLimit","value": 4200
}
查询派生类型Entity的派生类型属性的原始值
curl --location 'http://localhost:5119/odata/Customers(3)/Lesson8.Models.EnterpriseCustomer/CreditLimit/$value'
  • Response
4200

新增(POST)

而原始属性(如 Name、CreditLimit)只是实体的一个字段,不是集合或导航属性,不能单独“新增”,所以不支持 POST 操作。

完整更新(PUT)

Request MethodRoute Template说明
PUT~/{entityset}/{key}/{property}完整更新基类类型Entity的属性值
PUT~/{entityset}/{key}/{cast}/{property}完整更新派生类型Entity的属性值
PUT~/{singleton}/{property}完整更新基类类型单例的属性值
PUT~/{singleton}/{cast}/{property}完整更新派生类型单例的属性值

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

        public ActionResult PutToName([FromRoute] int key, [FromBody] string name){var customer = customers.SingleOrDefault(d => d.Id.Equals(key));if (customer == null){return NotFound();}customer.Name = name;return Ok();}
  • Request
curl --location --request PUT 'http://localhost:5119/odata/Customers(1)/Name' \
--header 'Content-Type: application/json' \
--data '{"value": "Sue"
}'

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

        public ActionResult PutToCreditLimitFromEnterpriseCustomer([FromRoute] int key, [FromBody] decimal creditLimit){var enterpriseCustomer = customers.OfType<EnterpriseCustomer>().SingleOrDefault(d => d.Id.Equals(key));if (enterpriseCustomer == null){return NotFound();}enterpriseCustomer.CreditLimit = creditLimit;return Ok();}
  • Request
curl --location --request PUT 'http://localhost:5119/odata/Customers(3)/Lesson8.Models.EnterpriseCustomer/CreditLimit' \
--header 'Content-Type: application/json' \
--data '{"value": 10
}'

局部更新(PATCH)

Patch操作支持单值复杂类型属性(single-valued complex properties),所以原始类型Property不支持Patch操作。

删除(DELETE)

Request MethodRoute Template说明
DELETE~/{entityset}/{key}/{nullableproperty}删除基类类型Entity的非空属性
DELETE~/{entityset}/{key}/{cast}/{nullableproperty}删除派生类型Entity的非空属性
DELETE~/{singleton}/{nullableproperty}删除基类类型单例的非空属性
DELETE~/{singleton}/{cast}/{nullableproperty}删除派生类型单例的非空属性

由于删除操作只能删除可设置为null的属性,所以我们需要在模型定义时,将需要操作的属性(Customer.Name和EnterpriseCustomer .CreditLimit )设置为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 DeleteToName([FromRoute] int key){var customer = customers.SingleOrDefault(d => d.Id.Equals(key));if (customer == null){return NotFound();}customer.Name = string.Empty; // Clear the namereturn NoContent();}
  • Request
curl --location --request DELETE 'http://localhost:5119/odata/Customers(1)/Name'

删除派生类型Entity的非空属性

        public ActionResult DeleteToCreditLimitFromEnterpriseCustomer([FromRoute] int key){var enterpriseCustomer = customers.OfType<EnterpriseCustomer>().SingleOrDefault(d => d.Id.Equals(key));if (enterpriseCustomer == null){return NotFound();}enterpriseCustomer.CreditLimit = 0; // Reset the credit limitreturn NoContent();}
  • Request
curl --location --request DELETE 'http://localhost:5119/odata/Customers(3)/Lesson8.Models.EnterpriseCustomer/CreditLimit'

主程序

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/BHBocwOlws.shtml

相关文章

PCIE之Lane Reserval通道out of oder调换顺序

参考&#xff1a;测量小百科 | PCIe通道位置翻转(Lane Reversal)技术 参考&#xff1a;PCIe学习笔记&#xff08;3&#xff09;链路初始化和训练_pcie 有序集 lane-CSDN博客 案例上都是按照x4或者x8交叉&#xff0c;对于x2也是有办法交叉的&#xff0c;如果4lane的顺序并不是…

LXQt修改开始菜单高亮

开始菜单红色高亮很难看 mkdir -p ~/.local/share/lxqt/palettes/ mkdir -p ~/.local/share/lxqt/themes/ cp /usr/share/lxqt/palettes/Dark ~/.local/share/lxqt/palettes/Darker cp -p /usr/share/lxqt/themes/dark ~/.local/share/lxqt/themes/darker lxqt-panel.qss L…

MIT 6.S081 2020 Lab6 Copy-on-Write Fork for xv6 个人全流程

文章目录 零、写在前面一、Implement copy-on write1.1 说明1.2 实现1.2.1 延迟复制与释放1.2.2 写时复制 零、写在前面 可以阅读下 《xv6 book》 的第五章中断和设备驱动。 问题 在 xv6 中&#xff0c;fork() 系统调用会将父进程的整个用户空间内存复制到子进程中。**如果父…

使用langchain实现RAG(检索增强生成)

概述 本文将从零开始实现一个langchain应用程序, 该应用支持读取pdf文档并embedding编码到Chroma数据库, 当用户提问时, 可以从网络搜索结果和本地向量数据库中收集数据, 传递给第三方LLM大模型, 所有使用到的工具完全免费 将使用如下技术或工具: python3.9langchainChroma …

力扣HOT100之动态规划:139. 单词拆分

这道题之前刷代码随想录的时候已经做过了&#xff0c;但是现在再做一遍还是不会&#xff0c;直接去看视频了。感觉这道题的dp数组很难想到&#xff0c;感觉做不出来也是情有可原吧。这道题目也是一个完全背包问题&#xff0c;字典里的单词就相当于物品&#xff0c;而字符串相当…

趋势直线指标

趋势直线副图和主图指标&#xff0c;旨在通过技术分析工具帮助交易者识别市场趋势和潜在的买卖点。 副图指标&#xff1a;基于KDJ指标的交易策略 1. RSV值计算&#xff1a; - RSV&#xff08;未成熟随机值&#xff09;反映了当前收盘价在过去一段时间内的相对位置。通过计算当前…

应急响应靶机-web3-知攻善防实验室

题目&#xff1a; 1.攻击者的两个IP地址 2.攻击者隐藏用户名称 3.三个攻击者留下的flag 密码&#xff1a;yj123456 解题&#xff1a; 1.攻击者的两个IP地址 一个可能是远程&#xff0c;D盾&#xff0c;404.php,192.168.75.129 找到远程连接相关的英文,1149代表远程连接成功…

前端-不对用户显示

这是steam的商店偏好设置界面&#xff0c;在没有被锁在国区的steam账号会有5个选项&#xff0c;而被锁在国区的账号只有3个选项&#xff0c;这里使用的技术手段仅仅在前端隐藏了这个其他两个按钮。 单击F12打开开发者模式 单击1处&#xff0c;找到这一行代码&#xff0c;可以看…

C++单调栈(递增、递减)

定义 先说单调栈的定义 单调栈&#xff0c;是指栈内数据逐步上升&#xff08;一个比一个大&#xff09;&#xff0c;或逐步下降&#xff08;一个比一个小&#xff09;的栈&#xff0c;其并没有独立的代码&#xff0c;而是在stack的基础上加以限制及条件形成的。 比如&#x…

WIN11+CUDA11.8+VS2019配置BundleFusion

参考&#xff1a; BundleFusion:VS2019 2017 ,CUDA11.5,win11&#xff0c;Realsense D435i离线数据包跑通&#xff0c;环境搭建 - 知乎 Win10VS2017CUDA10.1环境下配置BundleFusion - 知乎 BundleFusionWIN11VS2019 CUDA11.7环境配置-CSDN博客 我的环境&#xff1a;Win 11…

【基于SpringBoot的图书购买系统】Redis中的数据以分页的形式展示:从配置到前后端交互的完整实现

引言 在当今互联网应用开发中&#xff0c;高性能和高并发已经成为系统设计的核心考量因素。Redis作为一款高性能的内存数据库&#xff0c;以其快速的读写速度、丰富的数据结构和灵活的扩展性&#xff0c;成为解决系统缓存、高并发访问等场景的首选技术之一。在图书管理系统中&…

Leetcode LCR 187. 破冰游戏

1.题目基本信息 1.1.题目描述 社团共有 num 位成员参与破冰游戏&#xff0c;编号为 0 ~ num-1。成员们按照编号顺序围绕圆桌而坐。社长抽取一个数字 target&#xff0c;从 0 号成员起开始计数&#xff0c;排在第 target 位的成员离开圆桌&#xff0c;且成员离开后从下一个成员…

任务20:实现各省份平均气温预测

任务描述 知识点&#xff1a; 时间序列分析 重 点&#xff1a; 指数平滑法Python连接数据库&#xff0c;更新数据 内 容&#xff1a; 读取所有省份各月的平均气温数据预测各省份下一年1-12月的气温&#xff0c;并存储到MySQL数据库 任务指导 1. 读取所有省份各月的平…

【Unity】AudioSource超过MaxDistance还是能听见

unity版本&#xff1a;2022.3.51f1c1 将SpatialBlend拉到1即可 或者这里改到0 Hearing audio outside max distance - #11 by wderstine - Questions & Answers - Unity Discussions

VulnStack|红日靶场——红队评估四

信息收集及漏洞利用 扫描跟kali处在同一网段的设备&#xff0c;找出目标IP arp-scan -l 扫描目标端口 nmap -p- -n -O -A -Pn -v -sV 192.168.126.154 3个端口上有web服务&#xff0c;分别对应三个漏洞环境 &#xff1a;2001——Struts2、2002——Tomcat、2003——phpMyAd…

在 RK3588 上通过 VSCode 远程开发配置指南

在 RK3588 上通过 VSCode 远程开发配置指南 RK3588 设备本身不具备可视化编程环境&#xff0c;但可以通过 VSCode 的 Remote - SSH 插件 实现远程代码编写与调试。以下是完整的配置流程。 一、连接 RK3588 1. 安装 Debian 系统 先在 RK3588 上安装 Debian 操作系统。 2. 安…

Docker-搭建MySQL主从复制与双主双从

Docker -- 搭建MySQL主从复制与双主双从 一、MySQL主从复制1.1 准备工作从 Harbor 私有仓库拉取镜像直接拉取镜像运行容器 1.2 配置主、从服务器1.3 创建主、从服务器1.4 启动主库&#xff0c;创建同步用户1.5 配置启动从库1.6 主从复制测试 二、MySQL双主双从2.1 创建网络2.2 …

累加法求数列通项公式

文章目录 前言如何判断注意事项适用类型方法介绍典例剖析对应练习 前言 累加法&#xff0c;顾名思义&#xff0c;就是多次相加的意思。求通项公式题型中&#xff0c;如果给定条件最终可以转化为 a n 1 − a n f ( n ) a_{n1}-a_nf(n) an1​−an​f(n)的形式&#xff0c;或者…

vue3的watch用法

<template><div class"container mx-auto p-4"><h1 class"text-2xl font-bold mb-4">Vue 3 Watch 示例</h1><div class"grid grid-cols-1 md:grid-cols-2 gap-6"><!-- 基本数据监听 --><div class"…

day15 leetcode-hot100-28(链表7)

2. 两数相加 - 力扣&#xff08;LeetCode&#xff09; 1.模拟 思路 最核心的一点就是将两个链表模拟为等长&#xff0c;不足的假设为0&#xff1b; &#xff08;1&#xff09;设置一个新链表newl来代表相加结果。 &#xff08;2&#xff09;链表1与链表2相加&#xff0c;具…