本文屬於 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 檔案,並將其解析為客戶端程式碼。