OData WebAPI + Automapper 尘封的秘密(解决篇)

By | June 15, 2017

文章链接: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 vals 被标记为 static,值为 null。 可能的原因是: automapper 在查找映射关系时会利用反射获得 B 所有的属性,其中包括已经被隐藏的 List vals.

Leave a Reply

Your email address will not be published. Required fields are marked *