本文屬於 OData 系列 引言 在 OData 中,EDM(Entity Data Model) 代表“實體數據模型”,它是一種用於表示 Web API 中的結構化數據的格式。EDM 定義了可以由 OData 服務公開的數據類型、實體和關係。 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 模型,主要是有幾個方面:
- 實體框架中的實體類可能包含與 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 文件,並將其解析為客戶端代碼。