Web API中的DTO和预测

时间:2014-11-26 23:20:53

标签: asp.net entity-framework asp.net-web-api odata projection

WebAPI支持OData,因此API使用者可以指定他需要的字段,并且工作正常。但是有没有办法在WebAPI中使用DTO对象和投影?

例如我有

public class WebSite
{
    public string Url {get;set;}
    public string Author {get;set;}
    public string Technology {get;set;}
    public DateTime CreatedAt {get;set;}
    // 20 more different properties
}

我也有DTO对象:

public class WebSiteDTO
{
    public string Url {get;set;}
    public string Author {get;set;}

    public bool IsDotNet {get;set;} // it should be set during mapping as webSite.Technology == ".Net";
    public bool IsTrendThing {get;set;} // should be set as  webSite.Technology == ".Net" and webSite.CreatedAt > new DateTime(2014,0,0);
}

一些支持OData的典型WebAPI端点:

[HttpGet]
[Route("Test")]
public IQueryable Test(ODataQueryOptions<WebSiteDTO> options)
{
    var efDbContext = new MyDBContext();
    var query = efDbContext.WebSites;
    var odataQuery = options.ApplyTo(query, settings);
    return odataQuery;
}

在这种情况下,将返回WebSite对象。但是如何返回WebSiteDTO对象并仍然支持OData?是否可以按属性进行映射,而不是按类本身进行映射?就像通过OData请求Url一样,那么我们将从DB加载Url并将其映射到DTO对象中的Url属性?我可能有一些复杂的情况,DTO中的属性必须由IsDotNet示例中的某些自定义逻辑设置,或者它可能依赖于多个属性。

我想我可以编写一些自定义中间件,它们将使用OData指定的字段执行原始查询,然后将其保存在Dictionary中,然后执行类似的操作:

MyMapper.Map<WebSite, WebSiteDTO>().
    MapProperty(o, dict => o.Url = (string)dict["Url"]).
    MapProperty(o, dict => o.IsDotNet = (string)dict["Technology"] == ".Net").
    MapProperty(o, dict => o.IsTrendThing = (string)dict["Technology"] == ".Net" && (DateTime)dict["CreatedAt"] > new DateTime(2014,0,0));

但它看起来很难看,在这种情况下我需要指定如果在OData请求中请求了IsTrendThing属性,那么我还需要从WebSite对象加载Technology和CreatedAt字段,这会使事情变得复杂。

在我的情况下,有什么东西可以用吗?也许有人可以帮我正确指导?

自动映像has projections mapping但它在我的场景中不起作用,因为它需要特定的类型,在我的情况下,由于OData指定的字段,原始类型可能不同。

another similar question,但它是关于纯DTO,在我的情况下,我想支持&#39;选择\ expand&#39;来自OData的运算符作为我的对象可以具有很多属性。如果没有请求,我想不要从DB加载它们。

1 个答案:

答案 0 :(得分:2)

您应该在应用ODataQueryOptions之前进行投影。 ODataQueryOptions可以应用于WebSiteDto中存在的属性。

var date = new DateTime(2014,0,0);
// this will not load your data
var query = efDbContext.WebSites.Select(w => new WebSiteDto() 
{
    /* projection code */
    IsDotNet = w.Technology == ".Net",
    IsTrendThing = w.Technology == ".Net" && w.CreatedAt > date,
    Url = w.Url,
    Author = w.Author
});

// this will still not load your data but will be applied on your projected object
var odataQuery = options.ApplyTo(query, settings);

只有在控制器操作返回时才会执行完整查询(带有选项)。您可以在select方法中执行任何与LinqToEntities一起使用的投影代码(避免调用Equals等方法,使用==代替或调用任何构造函数)。您甚至不加载Technology或CreatedAt,查询在SQL服务器上执行,您只返回布尔值。

如果客户端将执行额外的$ select,它将被置于此查询之上,因此不必要的数据永远不会从SQL Server到达您的应用程序服务器(就像在IQueryable上应用另一个.Select())

我不确定AutoMapper如何处理IQueryable(如果它强制评估查询),但Select方法不会评估您的查询,只会构建它以便可以将它发送到SQL Server。