文章链接:http://simpeng.net/?p=175
最初了解 OData 是因为在工作中经常听同事们提起(主要是吐槽标准不同版本的兼容性,理论和实际的落差,”自以为是”的值得全人类遵循的 Web 数据协议标准)。刚刚入职的我,听到这些俨然一头雾水,不明觉厉。后来在项目中涉及客户端通过 REST API 的方式访问 SharePoint Document/Lists ,一组组的 API 调用 Pattern 也让我对 RESTFul 的 Protocal (以及 API performance 的底线) 有了最初的认识。再后来,参与开发了一个支持高并发的 RESTFul 数据 Pipeline,这里面用到了 OData Protocal V3 & V4、基于 OData Lib 和 OData WebAPI 之上的 RESTier Lib。 最近的项目中接触了更多地关于纯粹 OData WebAPI 的故事,总结总结,回忆回忆,以便将来不时之需。
另外, OData 相关的网上 Q&A实在有限,难道这个圈子真的真的这么小吗?但是真的真的一个一个问题都不好描述啊!!!
问题 1 描述: 在同一个 WebAPI 项目中存在多个 基于 ODataController 实现的Controller 类,路由映射失败
解决办法:https://stackoverflow.com/questions/27156638/adding-a-new-odata-controller-fails-existing-controller
原因分析:问题并不直接因多个 ODataController 的存在导致,问题往往发生在:为这些 ODataController 定义路由的时候,指定了多于一个 EDM Model。比如:
var config = new HttpConfiguration();
config.MapODataServiceRoute(routeName: “functions route”, routePrefix: “functions”, model: FunctionStartup.GetEdmModel());
config.MapODataServiceRoute(routeName: “actions route”, routePrefix: “actions”, model: ActionStartup.GetEdmModel());
在映射 OData 路由时,默认的 Controller 选择器使用 HttpConfiguration 的默认 Assembly Resolver 搜索所有的 Controller。因此,比如在作上述第一个映射的时候,由于搜索到的 Controller 里有 FunctionStartup.GetEdmModel() 没有描述的类型,映射失败。解决方法参照上述链接:通过自定义路由方法,限定每个 EDMModel 所对应的 Controller 类型。
问题 2 描述:父类、子类都需要作为 Entity Type,而且同时需要是 Open Entity Type
比如父子类定义分别如下:
class Parent {
int Id,
IDictionary<string, object> DynamicProperties; // 这会使得 Parent Entity Type 是一个 Open Entity Type
}
class Child: Parent {
string name,
}
EDM Model 的实现方法:
// Parent Entity Type
var pEntityConfig = builder.EntityType<Models.Parent>();
pEntityConfig.Property(q => q.Id).Name = “id”;
// Child Entity Type
var childExtEntityConfig = builder.EntityType<Models.Child>().DerivesFrom<Models.Parent>();
childExtEntityConfig.Property(q => q.Name).Name = “name”;
childExtEntityConfig.HasDynamicProperties(q => q.DynamicProperties); // 需要加上这句,才能让 Child Entity Type 成为 Open Entity Type
另外如果 EDM Builder 的实现是:
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.Namespace = ModelNamespace;
builder.EntitySet(“parent”);
builder.EnableLowerCamelCase();
builder.Ignore(); 最后一句如果不加,Child Entity 会在 metadata 里面自动出来。
问题 3 描述:为 Open Entity Type 做 Patch
如果需要支持 Patch 弱类型 (即 Parent 或者 Child 中不存在的属性)
[ODataRoute(“parent({id})”)]
public
async
Task<IHttpActionResult> PatchParent([FromODataUri]string id, [FromBody]Delta<Parent> patchParent)
patchParent.GetChangedPropertyNames() 中不包括弱类型的 key-value pair,所以需要对 Delta
internal static class DeltaExtensions
{
public static IDictionary<string, object> GetChangedPropertiesForOpenTypeEntity<T>(this Delta<T> delta)
{
var entity = delta?.GetEntity();
if (entity == null) {
return new Dictionary<string, object>();
}
var changedProps = delta.GetChangedProperties();
// Handle dynamic priperties for those entities supporting open types
var dynamicProps = typeof(T).GetProperty(nameof(entity.DynamicProperties))
.GetValue(entity) as IDictionary<string, object>;
if (dynamicProps?.Count > 0)
{
changedProps = changedProps.Union(dynamicProps).ToDictionary(pair => pair.Key, pair => pair.Value);
}
return changedProps;
}
}
问题 4 描述:Automapper 5.1.1 IncludeBase bug
https://github.com/AutoMapper/AutoMapper/issues/1743
ReverseMap ignores IncludeBase() calls. Everything works fine if you create separate reverse mapping.”
解决办法: create a separate reversemapping instead of using reverseMap, Or upgrade automapper to a version that includes the fix
问题 5 描述:Automapper 5.1.1 在 map 用 new 关键字修饰的属性时,会映射出两个值
比如
class A
{
int id,
List<string> vals,
}
class B: A
{
int Bid,
new List<int> vals, // new 关键词把父类的 vals 定义隐藏了
}
假设需要将 一个C 的实例 利用automapper 映射成类 B 的一个实例。
var bInstance = _modelMapper.Map<C, B>(cInstance);
得到的 bInstance 中会有两个 vals, List