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

原始属性(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 name

            return 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 limit

            return 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

参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

breaksoftware

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值