OData WebAPI实践-OData与EDM

EDM,WebAPI,OData · 浏览次数 : 310

小编点评

**EDM(Entity Data Model)** 是 OData 中用于表示 Web API 中的结构化数据的格式。它是一个 OData 服务的核心组成部分,允许客户端和服务器之间以一致的方式交换和操作数据。 **实体对象模型 (EF Core 实体对象)** 是用于在应用程序中表示数据库中的表或视图的类。它在 EF Core 中用于数据建模,并与 OData 服务中的数据模型不同。 **以下是两个概念之间的区别:** * **实体对象模型** 是一个针对特定数据库的类,用于存储数据库中的特定表。 * **EDM** 是一个 OData 服务中的通用数据模型,用于表示任何类型的 Web API 数据。 **在使用 EDM 时需要提供以下配置项:** * **实体类型**:定义实体类,包含属性和关系。 * **操作**:定义实体之间的关系,例如关联或继承。 * **数据结构**:定义数据结构,例如联合主键或自定义类型。 **使用 Convention 配置 EDM 有以下优点:** * 简化模型构建。 * 减少代码复杂性。 * 确保模型的正确性。

正文

本文属于 OData 系列

引言

在 OData 中,EDM(Entity Data Model) 代表“实体数据模型”,它是一种用于表示 Web API 中的结构化数据的格式。EDM 定义了可以由 OData 服务公开的数据类型、实体和关系。 EDM 也提供了一些规则来描述数据模型中的实体之间的关系,例如继承、关联和复合类型。EDM 是 OData 协议的核心组成部分之一,它允许客户端和服务器之间以一致的方式交换和操作数据。

EDM 与实体对象模型

我刚接触 EDM 时恰好是与 EF Core 一起使用,就非常不理解这个现象:明明已经在 EF Core 中已经定义了模型,为啥还需要单独配置一个 EDM?

其实,EDM 和实体框架(EF)Core 中的实体对象虽然都用于数据建模,但却是不同的概念:在 EF Core 中,实体对象表示数据库中的表或视图,而 EDM 定义了 OData 服务中的数据结构,包括实体、属性、导航属性等。可以理解实体对象是为数据库服务,而 EDM 是用于数据开放服务的

虽然 EDM 和 EF Core 中的实体对象都具有一些相似之处,例如它们都有属性和关系,甚至你也可以直接返回实体模型用提供给 OData 让其动态自动生成 EDM 模型(Non-ODM 模式),但是依然建议使用 EDM 模型,主要是有几个方面:

  1. 实体框架中的实体类可能包含与 OData 服务定义不同的属性。例如,实体框架中的实体类可能包含用于持久化和跟踪状态的属性,而这些属性可能并不需要在 OData 服务中公开。
  2. 实体框架中的实体类可能使用与 OData 服务定义不同的命名约定。例如,实体框架中的实体类可能使用 PascalCase(首字母大写)命名约定,而在 OData 服务中的 EDM 可以使用 camelCase(首字母小写)命名约定。
  3. 实体框架中的实体类可能包含与 OData 服务定义不同的数据结构。例如,联合主键的实现用于 OData 查询较为麻烦,可以配置 EDM 使得 OData 对外服务不使用联合主键。

EDM 配置

在配置 OData 时,我们需要在代码中提供 EDM 对象。

.AddRouteComponents(AuthorizeHelper.PREFIX, EdmHelper.GetEdmModels());

GetEdmModels 函数返回一个 IEdmModel 对象。

      public static IEdmModel GetEdmModels()
        {
            ODataConventionModelBuilder builder = new ODataConventionModelBuilder();

            var device = builder.EntitySet<DeviceInfo>("DeviceInfos").EntityType.HasKey(p => p.DeviceId);
			device.Action("Upload");
            builder.EnableLowerCamelCase();
            return builder.GetEdmModel();
        }

以上代码中对 DeviceInfo 类型定义了一个实体对象,其具有 DeviceId 作为主键,拥有一个名为 Upload 的 Action。并且对所有的 EDM 对象启用了 LowerCamelCase 支持。上面的感觉是挺简单的是吧,注意我们使用到了 ODataConventionModelBuilder 对象,这个对象帮助我们自动实现了很多配置内容。如果我们使用其他的方式就不那么简单了。实际上配置 EDM 总共有三种方式。

我一般只使用 Convention 的配置方法,因此这里引用官方网站的例子,详情请见 Introduction to the model builders - OData | Microsoft Learn

Explicit

如果模型通过 new EdmModel() 构建,那么构建的是无类型模型,相当于你不依赖现有的 CLR 类型凭空构建了一个模型。

public IEdmModel GetEdmModel()
{
    EdmModel model = new EdmModel();
    
	EdmEntityType customer = new EdmEntityType("WebApiDocNS", "Customer");
	customer.AddKeys(customer.AddStructuralProperty("CustomerId", EdmPrimitiveTypeKind.Int32));
	customer.AddStructuralProperty("Location", new EdmComplexTypeReference(address, isNullable: true));
	model.AddElement(customer);
	
	EdmEntityType order = new EdmEntityType("WebApiDocNS", "Order");
	order.AddKeys(order.AddStructuralProperty("OrderId", EdmPrimitiveTypeKind.Int32));
	order.AddStructuralProperty("Token", EdmPrimitiveTypeKind.Guid);
	model.AddElement(order);

    return model;
}

Non-convention

如果模型是依赖 new ODataModelBuilder() 构建,那么构建模型时可以依据现有的 CLR 对象进行构建,不过依然需要配置每一个属性、操作等。

public static IEdmModel GetEdmModel()
{
    var builder = new ODataModelBuilder();
    var customer = builder.EntityType<Customer>();
customer.HasKey(c => c.CustomerId);
customer.ComplexProperty(c => c.Location);
customer.HasMany(c => c.Orders);

	var order = builder.EntityType<Order>();
order.HasKey(o => o.OrderId);
order.Property(o => o.Token);
    return builder.GetEdmModel();
}

相当于前一种方法已经有了很大改进,我们可以依赖现有的结构,而不再需要手动去命名了。

Convention

更进一步,我们可以使用惯例 ( Convention )方式,模型依赖 new ODataConventionModelBuilder () 构建,这个是代码最少的,整个模型的配置按照 OData RESTful 惯例实现。

      public static IEdmModel GetEdmModels()
        {
            ODataConventionModelBuilder builder = new ODataConventionModelBuilder();

            var device = builder.EntitySet<DeviceInfo>("DeviceInfos").EntityType;
            return builder.GetEdmModel();
        }

Convention 涉及的内容很多,有机会以后会详细解释惯例生成 EDM 这种模式。

常用 EDM 配置

EDM 配置项目繁多,我们常用的有:

  • 配置实体(主键)
var device = builder.EntitySet<DeviceInfo>("DeviceInfos").EntityType;
  • 配置(集合) Action
//对于实体上的Action
device.Action("Upload");
//对于集合上的Action
device.Collection.Action("Upload");
  • 配置(集合) Function
//对于实体上的Function
device.Function("Data").Returns<string>();
//对于集合上的Function
device.Collection.Function("Data").Returns<string>();

对 Function 以及 Action 的详细介绍,请期待后续文章。

访问 EDM 模型

OData 服务提供了 $metadata 终结点,可以从服务的根 URL 后添加 $metadata 来访问。例如以下是一个可以直接访问的 EDM 模型,以 XML 形式提供:

http://services.odata.org/TripPinRESTierService/$metadata

此外,也可以使用某些工具(如 Microsoft 的 OData Connected Service)来自动生成客户端代码,并从服务中获取元数据。这些工具通常会从服务的根 URL 下载 $metadata 文件,并将其解析为客户端代码。

与OData WebAPI实践-OData与EDM相似的内容:

OData WebAPI实践-OData与EDM

本文属于 OData 系列 引言 在 OData 中,EDM(Entity Data Model) 代表“实体数据模型”,它是一种用于表示 Web API 中的结构化数据的格式。EDM 定义了可以由 OData 服务公开的数据类型、实体和关系。 EDM 也提供了一些规则来描述数据模型中的实体之间的关

OData WebAPI实践-Non-EDM模式

本文属于OData系列文章 前文说到了 EDM 与 OData 之间的关系,具有 EDM 的 OData 提供了强大的查询能力,但是 OData 并不必须要配置 EDM,我们也可以使用 Non-EDM 方案。 Non-EDM 所谓 Non-EDM ,并不是说在 OData 运行时不需要 EDM 配置

OData WebAPI实践-与ABP vNext集成

本文属于 OData 系列文章 ABP 是一个流行的 ASP. NET 开发框架,旧版的的 ABP 已经能够非常好的支持了 OData ,并提供了对应的 OData 包。 ABP vNext 是一个重新设计的,面向微服务的框架,提供了一些非常有用的特性,包括分页查询等但是它并不能原生支持 OData

OData WebAPI实践-兼容OData集合响应

本文属于 OData 系列文章 引言 OData 是一个开放标准,已经在 oasis 组织标准化,因此我们可以在标准的官网查询到 OData 的标准请求与返回形式:OData JSON Format Version 4.01 (oasis-open.org) 针对不同的数据类型,输出返回的格式也不尽

武装你的WEBAPI-OData之API版本管理

本文属于OData系列 Intro 对外提供WEBAPI时,如果遇上了版本升级,那么控制WEBAPI的版本也是非常必要的。OData官方提供了版本控制以及管理的解决方案,我个人是实践体会是不好用,好在社区提供了对应的nuget包,与.NET主版本同步更新。 介绍 ASP.NET API Versio

武装你的WEBAPI-OData与DTO

本文属于OData系列文章 Intro 前面写了很多有关OData使用的文章,很多读者会有疑问,直接将实体对象暴露给最终用户会不会有风险?$expand在默认配置的情况下,数据会不会有泄露风险? 答案是肯定的,由于OData的特性,提供给我们便捷同时也会带来一些风险。很多地方推荐使用DTO模式来隔离

武装你的WEBAPI-OData聚合查询

本文属于OData系列 Introduction ODATA v4提出了新的聚合查询功能,这对于ODATA的基本查询能力($expand等)是一个非常大的补充。ODATA支持的聚合查询功能,可以对数据进行统计分析,例如求和、平均值、最大/最小值、分组等。 聚合查询是通过$apply关键字实现的。使用

武装你的WEBAPI-OData使用Endpoint

本文属于 OData 系列文章 Introduction 更新: 由于新版的 OData 已经默认使用了 endpoint 模式(Microsoft.AspNetCore.OData 8.0.0),不再需要额外配置,本文已经过时(asp.net core 3.1)。 最近看 OData 的 devb

外键拆分手记

我习惯性使用OData,它的$expand与层级查询非常好用,这个功能非常依赖于数据库的导航属性,也就是外键结构。最近想着把一个单体的系统拆分为多个小系统,首先需要处理外键依赖的问题。 多个服务各自有各自的数据库,数据库层面并不互通,也就无法使用外键约束。 我使用EF Core来描述数据库的结构,有

ChatGPT与码农的机会

之前一篇博客已经写了有关AI在博客编写方面的优势与对未来博客的编写方面的思考。这篇文档我继续分享我在开发中的一个案例和相关的感想。 事件还原 我发现ChatGPT也可以帮助我编写OData,于是我也利用GPT帮助我编程。 OData如何将filter与apply字段联合使用?答案如下: GET /o