本文属于 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("DeviceInfos").EntityType.HasKey(p => p.DeviceId);device.Action("Upload"); builder.EnableLowerCamelCase(); return builder.GetEdmModel(); }
以上代码中对 DeviceInfo 类型定义了一个实体对象,其具有 DeviceId
作为主键,拥有一个名为 Upload
的 Action。并且对所有的 EDM 对象启用了 LowerCamelCase 支持。上面的感觉是挺简单的是吧,注意我们使用到了 ODataConventionModelBuilder
对象,这个对象帮助我们自动实现了很多配置内容。如果我们使用其他的方式就不那么简单了。实际上配置 EDM 总共有三种方式。
Explicit我一般只使用 Convention 的配置方法,因此这里引用官方网站的例子,详情请见 Introduction to the model builders - OData | Microsoft Learn
如果模型通过 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.HasKey(c => c.CustomerId);customer.ComplexProperty(c => c.Location);customer.HasMany(c => c.Orders);var order = builder.EntityType();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("DeviceInfos").EntityType; return builder.GetEdmModel(); }
常用 EDM 配置Convention 涉及的内容很多,有机会以后会详细解释惯例生成 EDM 这种模式。
EDM 配置项目繁多,我们常用的有:
配置实体(主键)var device = builder.EntitySet("DeviceInfos").EntityType;
配置(集合) Action//对于实体上的Actiondevice.Action("Upload");//对于集合上的Actiondevice.Collection.Action("Upload");
配置(集合) Function//对于实体上的Functiondevice.Function("Data").Returns();//对于集合上的Functiondevice.Collection.Function("Data").Returns();
访问 EDM 模型对 Function 以及 Action 的详细介绍,请期待后续文章。
OData 服务提供了 $metadata
终结点,可以从服务的根 URL 后添加 $metadata
来访问。例如以下是一个可以直接访问的 EDM 模型,以 XML 形式提供:
http://services.odata.org/TripPinRESTierService/$metadata
此外,也可以使用某些工具(如 Microsoft 的 OData Connected Service)来自动生成客户端代码,并从服务中获取元数据。这些工具通常会从服务的根 URL 下载 $metadata 文件,并将其解析为客户端代码。