ASP.NET Core OData 实践——Lesson10基本概念和动态构建(C#)

在现代的 Web 开发中,OData(开放数据协议)已经成为了一种非常流行的标准,用于构建和使用 RESTful API。ASP.NET Core 提供了强大的 OData 支持,使得开发者可以轻松地创建符合 OData 规范的 API。

本文主要实现了一个ASP.NET Core 应用程序,通过构建一个复杂的 OData 模型来支持 OData API 的开发。代码中定义了枚举类型、复杂类型、实体类型、导航属性、函数、动作等 OData 模型元素,并将它们组合成一个完整的 EdmModel。

关键概念和方法

枚举类型(EnumType)

  • 概念:枚举类型用于定义一组命名的常量值。在 OData 中,枚举类型可以作为实体属性的类型,使得数据具有更好的可读性和可维护性。
  • 应用场景:当某个属性的取值是有限的且固定的一组值时,使用枚举类型非常合适。例如,颜色、性别、状态等。
  • 特点:枚举类型的成员具有唯一的名称和对应的数值,方便在代码中进行比较和使用。
  • 与其他类型的区别:与复杂类型和实体类型不同,枚举类型不包含结构属性,只是一组简单的常量值。
static void addEnumType(EdmModel model)
{
    EdmEnumType color = new("Lesson10", "Color");
    color.AddMember(new EdmEnumMember(color, "Red", new EdmEnumMemberValue(0)));
    color.AddMember(new EdmEnumMember(color, "Blue", new EdmEnumMemberValue(1)));
    color.AddMember(new EdmEnumMember(color, "Green", new EdmEnumMemberValue(2)));
    model.AddElement(color);
}

Model Meta中的表现

            <EnumType Name="Color">
                <Member Name="Red" Value="0" />
                <Member Name="Blue" Value="1" />
                <Member Name="Green" Value="2" />
            </EnumType>

复杂类型(ComplexType)

  • 概念:复杂类型是一种包含多个结构属性的类型,但没有唯一标识符。它可以用于组合多个相关的属性,形成一个逻辑上的整体。
  • 应用场景:当需要将多个相关的属性组合在一起,并且这些属性不需要唯一标识时,可以使用复杂类型。例如,地址信息、联系人信息等。
  • 特点:复杂类型可以嵌套,即一个复杂类型的属性可以是另一个复杂类型。它通常用于,即一个复杂类型的属性可以是另一个复杂类型。它通常用于表示实体的一部分,而不是独立的实体。
  • 与其他类型的区别:与实体类型不同,复杂类型没有唯一标识符,不能独立存在,必须作为其他实体或复杂类型的一部分使用。与枚举类型不同,复杂类型包含多个结构属性,而不是简单的常量值。
static void addNonAbstractComplexType(EdmModel model)
{
    EdmComplexType address = new("Lesson10", "Address");
    address.AddStructuralProperty("Street", EdmPrimitiveTypeKind.String);
    address.AddStructuralProperty("City", EdmPrimitiveTypeKind.String);
    address.AddStructuralProperty("State", EdmPrimitiveTypeKind.String);
    address.AddStructuralProperty("Country", EdmPrimitiveTypeKind.String);
    model.AddElement(address);
}

Model Meta中的表现

            <ComplexType Name="Address">
                <Property Name="Street" Type="Edm.String" />
                <Property Name="City" Type="Edm.String" />
                <Property Name="State" Type="Edm.String" />
                <Property Name="Country" Type="Edm.String" />
            </ComplexType>

抽象

Abstract Complex Type(抽象复杂类型) 是一种特殊的复杂类型,其核心作用是定义一组派生类型的公共结构和行为,同时禁止直接实例化

// 定义抽象复杂类型
static void addAbstractComplexType(EdmModel model)
{
    EdmComplexType addressAbstract = new("Lesson10", "AddressAbstract", baseType: null, isAbstract: true);
    model.AddElement(addressAbstract);
}

Model Meta中的表现

<ComplexType Name="AddressAbstract" Abstract="true" />

继承与多态

Non-Abstract Complex Type可以继承于Abstract Complex Type或者Non-Abstract Complex Type。
Abstract Complex Type 也可以继承于Abstract Complex Type或者Non-Abstract Complex Type。

Non-Abstract Complex Type继承于Abstract Complex Type
static void addDerivedNonAbstractComplexTypeFromAbstractComplexType(EdmModel model)
{
    EdmComplexType derivedAddress = new("Lesson10", "DerivedAddressFromAbstract", baseType: model.FindType("Lesson10.AddressAbstract") as EdmComplexType, isAbstract: false);
    derivedAddress.AddStructuralProperty("Street", EdmPrimitiveTypeKind.String);
    derivedAddress.AddStructuralProperty("City", EdmPrimitiveTypeKind.String);
    model.AddElement(derivedAddress);
}

Model Meta中的表现

            <ComplexType Name="DerivedAddressFromAbstract" BaseType="Lesson10.AddressAbstract">
                <Property Name="Street" Type="Edm.String" />
                <Property Name="City" Type="Edm.String" />
            </ComplexType>
Non-Abstract Complex Type继承于Non-Abstract Complex Type
static void addDerivedNonAbstractComplexTypeFromNonAbstract(EdmModel model)
{
    EdmComplexType derivedAddress = new("Lesson10", "PostalAddress", baseType : model.FindType("Lesson10.Address") as EdmComplexType);
    derivedAddress.AddStructuralProperty("PostalCode", EdmPrimitiveTypeKind.String);
    model.AddElement(derivedAddress);
}

Model Meta中的表现

            <ComplexType Name="PostalAddress" BaseType="Lesson10.Address">
                <Property Name="PostalCode" Type="Edm.String" />
            </ComplexType>
Abstract Complex Type 可以继承于Abstract Complex Type
static void addDerivedAbstractComplexTypeFromAbstractComplexType(EdmModel model)
{
    EdmComplexType derivedAddress = new("Lesson10", "DerivedAddress", baseType: model.FindType("Lesson10.AddressAbstract") as EdmComplexType, isAbstract: false);
    derivedAddress.AddStructuralProperty("Street", EdmPrimitiveTypeKind.String);
    derivedAddress.AddStructuralProperty("City", EdmPrimitiveTypeKind.String);
    model.AddElement(derivedAddress);
}

Model Meta中的表现

            <ComplexType Name="DerivedAddress" BaseType="Lesson10.AddressAbstract">
                <Property Name="Street" Type="Edm.String" />
                <Property Name="City" Type="Edm.String" />
            </ComplexType>
Abstract Complex Type 可以Non-Abstract Complex Type
static void addDerivedAbstractComplexTypeFromNonAbstractComplexType(EdmModel model)
{
    EdmComplexType derivedAddress = new("Lesson10", "DerivedAddressFromNonAbstract", baseType: model.FindType("Lesson10.Address") as EdmComplexType, isAbstract: false);
    derivedAddress.AddStructuralProperty("Street", EdmPrimitiveTypeKind.String);
    derivedAddress.AddStructuralProperty("City", EdmPrimitiveTypeKind.String);
    model.AddElement(derivedAddress);
}

Model Meta中的表现

            <ComplexType Name="DerivedAddressFromNonAbstract" BaseType="Lesson10.Address">
                <Property Name="Street" Type="Edm.String" />
                <Property Name="City" Type="Edm.String" />
            </ComplexType>

实体类型(EntityType)

  • 概念:实体类型是 OData 模型中最重要的概念之一,它表示具有唯一标识符的实体。实体类型可以包含多个结构属性和导航属性,用于描述实体的特征和关系。
  • 应用场景:当需要表示具有独立存在意义的对象时,使用实体类型。例如,客户、订单、产品等。
  • 特点:实体类型必须有一个或多个键属性,用于唯一标识实体。它可以与其他实体类型建立关联关系,通过导航属性进行访问。
  • 与其他类型的区别:与复杂类型不同,实体类型有唯一标识符,可以独立存在。与枚举类型不同,实体类型包含多个结构属性和导航属性,而不是简单的常量值。
static void addNonAbstractEntityType(EdmModel model)
{
    EdmEntityType customer = new("Lesson10", "Customer");
    customer.AddKeys(customer.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32));
    customer.AddStructuralProperty("Name", EdmPrimitiveTypeKind.String);
    var postalAddressType = model.FindType("Lesson10.PostalAddress") as IEdmComplexType;
    if (postalAddressType != null)
    {
        customer.AddStructuralProperty("PostalAddress", new EdmComplexTypeReference(postalAddressType, true));
    }
    model.AddElement(customer);
}

Model Meta中的表现

            <EntityType Name="Customer">
                <Key>
                    <PropertyRef Name="Id" />
                </Key>
                <Property Name="Id" Type="Edm.Int32" />
                <Property Name="Name" Type="Edm.String" />
                <Property Name="PostalAddress" Type="Lesson10.PostalAddress" />
                <NavigationProperty Name="Orders" Type="Collection(Lesson10.Order)" />
            </EntityType>

抽象

抽象实体类型(Abstract EntityType) 是一种特殊的实体类型,其核心作用是定义一组派生实体的公共结构和行为,同时禁止直接实例化。

static void addAbstractEntityType(EdmModel model)
{
    EdmEntityType orderAbstract = new("Lesson10", "OrderAbstract", baseType: null, isAbstract: true, isOpen: false);
    orderAbstract.AddKeys(orderAbstract.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32));
    model.AddElement(orderAbstract);
}

Model Meta中的表现

            <EntityType Name="OrderAbstract" Abstract="true">
                <Key>
                    <PropertyRef Name="Id" />
                </Key>
                <Property Name="Id" Type="Edm.Int32" />
            </EntityType>

继承

Non-Abstract Entity Type可以继承于Abstract Entity Type或者Non-Abstract Entity Type。
Abstract Entity Type 也可以继承于Abstract Entity Type或者Non-Abstract Entity Type。

Non-Abstract Entity Type继承于Abstract Entity Type

static void addDerivedNonAbstractEntityTypeFromAbstractComplexType(EdmModel model)
{
    EdmEntityType order = new("Lesson10", "OrderFromAbstract", baseType: model.FindType("Lesson10.OrderAbstract") as IEdmEntityType, isAbstract: false, isOpen: false);
    order.AddStructuralProperty("Amount", EdmPrimitiveTypeKind.Decimal);
    model.AddElement(order);
}

Model Meta中的表现

            <EntityType Name="OrderFromAbstract" BaseType="Lesson10.OrderAbstract">
                <Property Name="Amount" Type="Edm.Decimal" Scale="variable" />
            </EntityType>

Non-Abstract Entity Type继承于Non-Abstract Entity Type

static void addDerivedNonAbstractEntityTypeFromNonAbstractEntityType(EdmModel model)
{
    EdmEntityType vipCustomer = new("Lesson10", "VipCustomer", baseType: model.FindType("Lesson10.Customer") as IEdmEntityType);
    vipCustomer.AddStructuralProperty("VipLevel", EdmPrimitiveTypeKind.String);
    model.AddElement(vipCustomer);
}

Model Meta中的表现

            <EntityType Name="VipCustomer" BaseType="Lesson10.Customer">
                <Property Name="VipLevel" Type="Edm.String" />
            </EntityType>

Abstract Entity Type 可以继承于Abstract Entity Type

static void addDerivedAbstractEntityTypeFromAbastractEntityType(EdmModel model)
{
    EdmEntityType order = new("Lesson10", "Order", baseType: model.FindType("Lesson10.OrderAbstract") as IEdmEntityType, isAbstract: true, isOpen: false);
    order.AddStructuralProperty("Amount", EdmPrimitiveTypeKind.Decimal);
    model.AddElement(order);
}

Model Meta中的表现

            <EntityType Name="Order" BaseType="Lesson10.OrderAbstract" Abstract="true">
                <Property Name="Amount" Type="Edm.Decimal" Scale="variable" />
                <NavigationProperty Name="Customer" Type="Lesson10.Customer" Nullable="false" />
            </EntityType>

Abstract Entity Type继承于Non-Abstract Entity Type

static void addDerivedAbstractEntityTypeFromNonAbstractEntityType(EdmModel model)
{
    EdmEntityType order = new("Lesson10", "OrderFromNonAbstract", baseType: model.FindType("Lesson10.Customer") as IEdmEntityType, isAbstract: false, isOpen: false);
    order.AddStructuralProperty("Amount", EdmPrimitiveTypeKind.Decimal);
    model.AddElement(order);
}

Model Meta中的表现

            <EntityType Name="OrderFromNonAbstract" BaseType="Lesson10.Customer">
                <Property Name="Amount" Type="Edm.Decimal" Scale="variable" />
            </EntityType>

实体集合(Entity Set)

  • 概念:同一实体类型的集合(类似数据库表),支持 CRUD 操作。
  • 应用场景:多实例资源(用户列表、订单集合)。
  • 特点:需键值访问单个实体,支持集合查询($filter)。
  • 与其他类型的区别:与 EntityType 是 “集合 - 元素” 关系。
		var container = model.EntityContainer as EdmEntityContainer;
        var customerType = model.FindType("Lesson10.Customer") as IEdmEntityType;
        if (customerType != null)
        {
            container.AddEntitySet("Customers", customerType);
        }

Model Meta中的表现

 <EntitySet Name="Customers" EntityType="Lesson10.Customer" />

单例(Singleton)

  • 概念:全局唯一的单个实体,无需键值定位。
  • 应用场景:唯一资源(系统配置、租户信息)。
  • 特点:直接通过名称访问,不支持集合操作(如批量删除)。
  • 与其他类型的区别:与 EntityType 是 “实例 - 类型” 关系。
    	var container = model.EntityContainer as EdmEntityContainer;
        var singleCustomerType = model.FindType("Lesson10.SingleCustomer") as IEdmEntityType;
        if (singleCustomerType != null)
        {
            container.AddSingleton("SingleCustomer", singleCustomerType);
        }

Model Meta中的表现

<Singleton Name="SingleCustomer" Type="Lesson10.SingleCustomer" />

导航属性(NavigationProperty)

  • 概念:导航属性用于表示实体之间的关联关系。通过导航属性,客户端可以在查询实体时同时获取相关联的实体数据。
  • 应用场景:当实体之间存在关联关系时,使用导航属性可以方便地进行数据的查询和访问。例如,客户和订单之间的关系,一个客户可以有多个订单,一个订单只属于一个客户。
  • 特点:导航属性可以是单向的或双向的,根据关联关系的性质而定。它可以指定目标实体的多重性,如一对一、一对多、多对多等。
  • 与其他类型的区别:导航属性不是实体的结构属性,而是用于表示实体之间的关系。它不包含具体的数据,而是指向相关联的实体。
static void addNavigationProperty(EdmModel model)
{
    EdmEntityType? customer = model.FindType("Lesson10.Customer") as EdmEntityType;
    EdmEntityType? order = model.FindType("Lesson10.Order") as EdmEntityType;
    if (customer != null && order != null)
    {
        customer.AddUnidirectionalNavigation(new EdmNavigationPropertyInfo
        {
            Name = "Orders",
            TargetMultiplicity = EdmMultiplicity.Many,
            Target = order,
            ContainsTarget = false
        });
        order.AddUnidirectionalNavigation(new EdmNavigationPropertyInfo
        {
            Name = "Customer",
            TargetMultiplicity = EdmMultiplicity.One,
            Target = customer,
            ContainsTarget = false
        });
    }
}

Model Meta中的表现

            <EntityType Name="Customer">
                <Key>
                    <PropertyRef Name="Id" />
                </Key>
                <Property Name="Id" Type="Edm.Int32" />
                <Property Name="Name" Type="Edm.String" />
                <Property Name="PostalAddress" Type="Lesson10.PostalAddress" />
                <NavigationProperty Name="Orders" Type="Collection(Lesson10.Order)" />
            </EntityType>
       
            <EntityType Name="Order" BaseType="Lesson10.OrderAbstract" Abstract="true">
                <Property Name="Amount" Type="Edm.Decimal" Scale="variable" />
                <NavigationProperty Name="Customer" Type="Lesson10.Customer" Nullable="false" />
            </EntityType>

函数(Function)

  • 概念:函数是用于执行特定操作的 OData 元素,通常用于查询数据,返回一个结果;
  • 应用场景:函数适用于需要根据特定条件查询数据的场景,如根据客户 ID 获取客户信息。
  • 特点:函数可以是无绑定的或绑定的,无绑定函数可以直接调用,绑定函数需要绑定到某个实体或实体集上。
  • 与其他类型的区别:函数与实体类型、复杂类型等不同,它不是用于表示数据结构,而是用于执行操作。Function(函数)主要用于执行查询操作并返回数据(如根据条件获取实体),而 Action(动作)主要用于执行命令式操作(如创建、更新、删除实体或触发业务逻辑),通常不直接返回查询结果但可能有执行状态反馈。

Unbound Function(无绑定函数)

  • 定义:独立的服务操作,不绑定到特定实体或实体集。
  • 调用方式:直接通过容器路径访问。
  • 应用场景:全局查询、与实体无关的计算。
static void addUnboundFunction(EdmModel model)
{
    EdmFunction function = new("Lesson10", "GetCustomerById", new EdmEntityTypeReference(model.FindType("Lesson10.Customer") as IEdmEntityType, true), isBound: false, entitySetPathExpression: null, isComposable: false);
    function.AddParameter("key", EdmCoreModel.Instance.GetInt32(false));
    model.AddElement(function);
}

Model Meta中的表现

            <Function Name="GetCustomerById">
                <Parameter Name="key" Type="Edm.Int32" Nullable="false" />
                <ReturnType Type="Lesson10.Customer" />
            </Function>

Bound Function(绑定函数)

  • 定义:绑定到特定实体类型或实体集的函数。
  • 调用方式:通过实体实例或实体集路径访问。
  • 应用场景:基于特定实体的计算(如 Customer.GetOrdersByYear)。
static void addBoundFunction(EdmModel model)
{
    EdmFunction function = new("Lesson10", "GetCustomerName", EdmCoreModel.Instance.GetString(false), isBound: true, entitySetPathExpression: null, isComposable: false);
    function.AddParameter("customer", new EdmEntityTypeReference(model.FindType("Lesson10.Customer") as IEdmEntityType, false));
    model.AddElement(function);
}

Model Meta中的表现

            <Function Name="GetCustomerName" IsBound="true">
                <Parameter Name="customer" Type="Lesson10.Customer" Nullable="false" />
                <ReturnType Type="Edm.String" Nullable="false" />
            </Function>

动作(Action)

  • 概念:动作用于执行特定操作的 OData 元素,通常用于执行一些副作用操作,如更新、删除等。
  • 应用场景:动作适用于需要执行一些业务逻辑的场景,如更新客户信息、删除订单等。
  • 特点:动作可以是无绑定的或绑定的,并且可以有返回值或没有返回值。
  • 与其他类型的区别:动作与实体类型、复杂类型等不同,它不是用于表示数据结构,而是用于执行操作。Function(函数)主要用于执行查询操作并返回数据(如根据条件获取实体),而 Action(动作)主要用于执行命令式操作(如创建、更新、删除实体或触发业务逻辑),通常不直接返回查询结果但可能有执行状态反馈。

Unbound Action(无绑定动作)

  • 定义:独立的服务操作,不绑定到特定实体。
  • 调用方式:直接通过容器路径访问。
  • 应用场景:全局命令(如重置缓存、发送通知)。
static void addUnboundAction(EdmModel model)
{
    EdmAction action = new("Lesson10", "UpdateCustomer", EdmCoreModel.Instance.GetString(false), isBound: false, entitySetPathExpression: null);
    action.AddParameter("customer", new EdmEntityTypeReference(model.FindType("Lesson10.Customer") as IEdmEntityType, false));
    model.AddElement(action);
}

Model Meta中的表现

            <Action Name="UpdateCustomer">
                <Parameter Name="customer" Type="Lesson10.Customer" Nullable="false" />
                <ReturnType Type="Edm.String" Nullable="false" />
            </Action>

Bound Action(绑定动作)

  • 定义:绑定到特定实体类型或实体集的动作。
  • 调用方式:通过实体实例路径访问。
  • 应用场景:对特定实体执行操作(如 Customer.PlaceOrder)。
static void addBoundAction(EdmModel model)
{
    EdmAction action = new("Lesson10", "UpdateCustomerName", EdmCoreModel.Instance.GetString(false), isBound: true, entitySetPathExpression: null);
    action.AddParameter("customer", new EdmEntityTypeReference(model.FindType("Lesson10.Customer") as IEdmEntityType, false));
    action.AddParameter("name", EdmCoreModel.Instance.GetString(false));
    model.AddElement(action);
}

Model Meta中的表现

            <Action Name="UpdateCustomerName" IsBound="true">
                <Parameter Name="customer" Type="Lesson10.Customer" Nullable="false" />
                <Parameter Name="name" Type="Edm.String" Nullable="false" />
                <ReturnType Type="Edm.String" Nullable="false" />
            </Action>

函数导入(FunctionImport)和动作导入(ActionImport)

  • 概念:函数导入和动作导入是将函数和动作暴露给客户端的一种方式。通过函数导入和动作导入,客户端可以通过 URL 调用函数和动作。
  • 应用场景:当需要将函数和动作提供给客户端使用时,使用函数导入和动作导入。例如,客户端可以通过调用函数导入获取特定客户的信息,通过调用动作导入更新客户信息。
  • 特点:函数导入和动作导入需要添加到实体容器中,以便客户端可以通过 URL 访问。它们可以有参数和返回值,客户端可以在 URL 中传递参数。
  • 与其他类型的区别:函数导入和动作导入是用于将函数和动作暴露给客户端的机制,与函数和动作本身不同,它们是在实体容器层面进行管理的。
static void addFunctionImport(EdmModel model)
{
    var container = model.EntityContainer as EdmEntityContainer;
    if (container != null)
    {
        var function = model.FindDeclaredOperations("Lesson10.GetCustomerById").FirstOrDefault() as IEdmFunction;
        if (function != null)
        {
            container.AddFunctionImport("GetCustomerById", function);
        }
    }
}

static void addActionImport(EdmModel model)
{
    var container = model.EntityContainer as EdmEntityContainer;
    if (container != null)
    {
        var action = model.FindDeclaredOperations("Lesson10.UpdateCustomer").FirstOrDefault() as IEdmAction;
        if (action != null)
        {
            container.AddActionImport("UpdateCustomer", action);
        }
    }
}

Model Meta中的表现

                <FunctionImport Name="GetCustomerById" Function="Lesson10.GetCustomerById" />
                <ActionImport Name="UpdateCustomer" Action="Lesson10.UpdateCustomer" />

实体容器(Container)

  • 概念:在 OData 模型中,实体容器(Entity Container) 是模型的核心容器,用于封装实体集(Entity Sets)、函数导入(Function Imports)、动作导入(Action Imports)等运行时可访问的资源。当模型未显式指定容器名称时,OData 会自动创建一个默认容器。
  • 应用场景:所有 OData 模型必须至少包含一个实体容器,默认容器是最常用的方式。
  • 特点:集中管理可访问的实体集、函数、动作,确保客户端通过统一入口访问资源。
static void addDefaultContainer(EdmModel model)
{
    EdmEntityContainer container = new("Lesson10", "DefaultContainer");
    model.AddElement(container);
}

Model Meta中的表现

                <FunctionImport Name="GetCustomerById" Function="Lesson10.GetCustomerById" />
                <ActionImport Name="UpdateCustomer" Action="Lesson10.UpdateCustomer" />
                <EntitySet Name="Customers" EntityType="Lesson10.Customer" />
                <Singleton Name="SingleCustomer" Type="Lesson10.SingleCustomer" />

代码地址

https://github.com/f304646673/odata/tree/main/csharp/Lesson/Lesson10

参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

breaksoftware

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

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

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

打赏作者

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

抵扣说明:

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

余额充值