OData WebAPI实践-OData与EDM

2023-06-14,,

本文属于 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 模型,主要是有几个方面:

    实体框架中的实体类可能包含与 OData 服务定义不同的属性。例如,实体框架中的实体类可能包含用于持久化和跟踪状态的属性,而这些属性可能并不需要在 OData 服务中公开。
    实体框架中的实体类可能使用与 OData 服务定义不同的命名约定。例如,实体框架中的实体类可能使用 PascalCase(首字母大写)命名约定,而在 OData 服务中的 EDM 可以使用 camelCase(首字母小写)命名约定。
    实体框架中的实体类可能包含与 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.doc》

下载本文的Word格式文档,以方便收藏与打印。