使用DTO与OData& Web API [英] Using DTO's with OData & Web API

查看:151
本文介绍了使用DTO与OData& Web API的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用Web API和OData,我有一个公开数据传输对象而不是实体框架实体的服务。



我使用AutoMapper使用 ProjectTo()将EF实体转换为DTO计数器零件:

  public class SalesOrdersController:ODataController 
{
private DbContext _DbContext;

public SalesOrdersController(DbContext context)
{
_DbContext = context;
}

[EnableQuery]
public IQueryable< SalesOrderDto> Get(ODataQueryOptions< SalesOrderDto> queryOptions)
{
返回_DbContext.SalesOrders.ProjectTo< SalesOrderDto>(AutoMapperConfig.Config);
}

[EnableQuery]
public IQueryable< SalesOrderDto> Get([FromODataUri] string key,ODataQueryOptions&Sales; SalesOrderDto> queryOptions)
{
return _DbContext.SalesOrders.Where(so => so.SalesOrderNumber == key)
.ProjectTo< SalesOrderDto> (AutoMapperConfig.Config);
}
}

AutoMapper(V4.2.1)配置如下,请注意 ExplicitExpansion(),阻止序列化在不请求时自动展开导航属性:

 code> cfg.CreateMap< SalesOrderHeader,SalesOrderDto>()
.ForMember(dest => dest.SalesOrderLines,opt => opt.ExplicitExpansion());

cfg.CreateMap< SalesOrderLine,SalesOrderLineDto>()
.ForMember(dest => dest.MasterStockRecord,opt => opt.ExplicitExpansion())
.ForMember dest => dest.SalesOrderHeader,opt => opt.ExplicitExpansion());

ExplicitExpansion()然后创建一个新问题其中以下请求引发错误:


/ odatademo / SalesOrders('123456')?$ expand = SalesOrderLines



URI中指定的查询无效。在LINQ to Entities中不支持指定的类型成员SalesOrderLines


导航属性 SalesOrderLines 对于EF来说是未知的,所以这个错误几乎是我预期会发生的。问题是如何处理这种类型的请求?



ProjectTo()方法确实有一个重载这允许我通过一系列需要扩展的属性,我发现&修改扩展方法 ToNavigationPropertyArray 以尝试将该请求解析为字符串数组:

  [EnableQuery] 
public IQueryable< SalesOrderDto> Get([FromODataUri] string key,ODataQueryOptions&Sales; SalesOrderDto> queryOptions)
{
return _DbContext.SalesOrders.Where(so => so.SalesOrderNumber == key)
.ProjectTo< SalesOrderDto> (AutoMapperConfig.Config,null,queryOptions.ToNavigationPropertyArray());


public static string [] ToNavigationPropertyArray(这个ODataQueryOptions源)
{
if(source == null){return new string [] {}; }

var expandProperties = string.IsNullOrWhiteSpace(source.SelectExpand?.RawExpand)?新的List< string>()。ToArray():source.SelectExpand.RawExpand.Split(',');

for(var expandIndex = 0; expandIndex< expandProperties.Length; expandIndex ++)
{
//需要将扩展​​属性的odata语法转换为EF会理解的东西:

// OData可以传递以下形式的内容:SalesOrderLines($ expand = MasterStockRecord);
//但是EF想要这样:SalesOrderLines.MasterStockRecord;

expandProperties [expandIndex] = expandProperties [expandIndex] .Replace(,);
expandProperties [expandIndex] = expandProperties [expandIndex] = expandProperties [expandIndex] .Replace(($ expand =,。);
expandProperties [expandIndex] );
}

var selectProperties = source.SelectExpand == null || string.IsNullOrWhiteSpace(source.SelectExpand.RawSelect)?新列表< string>()。ToArray():source.SelectExpand.RawSelect.Split(',');

//现在为Select(不完整)执行相同的操作
var propertiesToExpand = expandProperties.Union(selectProperties).ToArray();

return propertiesToExpand;
}

这适用于展开,所以现在我可以处理像下面这样的请求:


/ odatademo / SalesOrders('123456')?$ expand = SalesOrderLines


或更复杂的请求,如:


/ odatademo / SalesOrders('123456')?$ expand = SalesOrderLines($ expand = MasterStockRecord)


然而,更复杂的请求尝试结合$ select和$ expand将失败:


/ odatademo / SalesOrders('123456')?$ expand = SalesOrderLines($ select = OrderQuantity)



序列不包含元素




所以问题是:我正是这样做吗?
感觉非常臭,我必须写一些东西来解析,并将ODataQueryOptions转换成EF可以理解的东西。



似乎这是一个颇受欢迎的话题:





虽然大多数建议使用 Projec如果 ExplictExpansion 已被配置,那么没有一个似乎无法解决序列化自动展开属性,或者如何处理扩展。



下面的类和配置



实体框架(V6.1.3)实体:

  public class SalesOrderHeader 
{
public string SalesOrderNumber {get;组; }
public string Alpha {get;组; }
public string Customer {get;组; }
public string Status {get;组; }
public virtual ICollection< SalesOrderLine> SalesOrderLines {get;组; }
}

public class SalesOrderLine
{
public string SalesOrderNumber {get;组; }
public string OrderLineNumber {get;组; }
public string Product {get;组; }
public string描述{get;组; }
public decimal OrderQuantity {get;组; }

public virtual SalesOrderHeader SalesOrderHeader {get;组; }
public virtual MasterStockRecord MasterStockRecord {get;组; }
}

public class MasterStockRecord
{
public string ProductCode {get;组; }
public string描述{get;组; }
public decimal数量{get;组;
}

OData(V6.13.0)数据传输对象:

  public class SalesOrderDto 
{
[Key]
public string SalesOrderNumber {get;组; }
public string Customer {get;组; }
public string Status {get;组; }
public virtual ICollection< SalesOrderLineDto> SalesOrderLines {get;组;
}

public class SalesOrderLineDto
{
[Key]
[ForeignKey(SalesOrderHeader)]
public string SalesOrderNumber {get ;组;

[Key]
public string OrderLineNumber {get;组; }
public string LineType {get;组; }
public string Product {get;组; }
public string描述{get;组; }
public decimal OrderQuantity {get;组; }

public virtual SalesOrderDto SalesOrderHeader {get;组; }
public virtual StockDto MasterStockRecord {get;组;
}

public class StockDto
{
[Key]
public string StockCode {get;组; }
public string描述{get;组; }
public decimal数量{get;组;
}

OData配置:

  var builder = new ODataConventionModelBuilder(); 

builder.EntitySet&StockDto>(Stock);
builder.EntitySet< SalesOrderDto>(SalesOrders);
builder.EntitySet< SalesOrderLineDto>(SalesOrderLines);


解决方案

我从来没有真正设法工作。 ToNavigationPropertyArray()扩展方法有一点帮助,但不能处理无限深度导航。



真正的解决方案是创建操作或函数,以允许客户端请求需要更复杂查询的数据。另一个选择是使多个较小/简单的调用然后在客户端上聚合数据,但这不是真正的理想。


Using Web API and OData, I have a service which exposes Data Transfer Objects instead of the Entity Framework entities.

I use AutoMapper to transform the EF Entities into their DTO counter parts using ProjectTo():

public class SalesOrdersController : ODataController
{
    private DbContext _DbContext;

    public SalesOrdersController(DbContext context)
    {
        _DbContext = context;
    }

    [EnableQuery]
    public IQueryable<SalesOrderDto> Get(ODataQueryOptions<SalesOrderDto> queryOptions)
    {
        return _DbContext.SalesOrders.ProjectTo<SalesOrderDto>(AutoMapperConfig.Config);
    }

    [EnableQuery]
    public IQueryable<SalesOrderDto> Get([FromODataUri] string key, ODataQueryOptions<SalesOrderDto> queryOptions)
    {
        return _DbContext.SalesOrders.Where(so => so.SalesOrderNumber == key)
                            .ProjectTo<SalesOrderDto>(AutoMapperConfig.Config);
    }
}

AutoMapper (V4.2.1) is configured as follows, note the ExplicitExpansion() which prevents serialisation auto expanding navigation properties when they are not requested:

cfg.CreateMap<SalesOrderHeader, SalesOrderDto>()                
            .ForMember(dest => dest.SalesOrderLines, opt => opt.ExplicitExpansion());

cfg.CreateMap<SalesOrderLine, SalesOrderLineDto>()
            .ForMember(dest => dest.MasterStockRecord, opt => opt.ExplicitExpansion())
            .ForMember(dest => dest.SalesOrderHeader, opt => opt.ExplicitExpansion());

ExplicitExpansion() then creates a new problem where the following request throws an error:

/odatademo/SalesOrders('123456')?$expand=SalesOrderLines

The query specified in the URI is not valid. The specified type member 'SalesOrderLines' is not supported in LINQ to Entities

The navigation property SalesOrderLines is unknown to EF so this error is pretty much what I expected to happen. The question is, how do I handle this type of request?

The ProjectTo() method does have an overload that allows me to pass in an array of properties that require expansion, I found & modified the extension method ToNavigationPropertyArray to try and parse the request into a string array:

[EnableQuery]
public IQueryable<SalesOrderDto> Get([FromODataUri] string key, ODataQueryOptions<SalesOrderDto> queryOptions)
{
    return _DbContext.SalesOrders.Where(so => so.SalesOrderNumber == key)
            .ProjectTo<SalesOrderDto>(AutoMapperConfig.Config, null, queryOptions.ToNavigationPropertyArray());
}

public static string[] ToNavigationPropertyArray(this ODataQueryOptions source)
{
    if (source == null) { return new string[]{}; }

    var expandProperties = string.IsNullOrWhiteSpace(source.SelectExpand?.RawExpand) ? new List<string>().ToArray() : source.SelectExpand.RawExpand.Split(',');

    for (var expandIndex = 0; expandIndex < expandProperties.Length; expandIndex++)
    {
        // Need to transform the odata syntax for expanding properties to something EF will understand:

        // OData may pass something in this form: "SalesOrderLines($expand=MasterStockRecord)";                
        // But EF wants it like this: "SalesOrderLines.MasterStockRecord";

        expandProperties[expandIndex] = expandProperties[expandIndex].Replace(" ", "");
        expandProperties[expandIndex] = expandProperties[expandIndex].Replace("($expand=", ".");
        expandProperties[expandIndex] = expandProperties[expandIndex].Replace(")", "");
    }

    var selectProperties = source.SelectExpand == null || string.IsNullOrWhiteSpace(source.SelectExpand.RawSelect) ? new List<string>().ToArray() : source.SelectExpand.RawSelect.Split(',');

    //Now do the same for Select (incomplete)          
    var propertiesToExpand = expandProperties.Union(selectProperties).ToArray();

    return propertiesToExpand;
}

This works for expand, so now I can handle a request like the following:

/odatademo/SalesOrders('123456')?$expand=SalesOrderLines

or a more complicated request like:

/odatademo/SalesOrders('123456')?$expand=SalesOrderLines($expand=MasterStockRecord)

However, more complicated request that try to combine $select with $expand will fail:

/odatademo/SalesOrders('123456')?$expand=SalesOrderLines($select=OrderQuantity)

Sequence contains no elements

So, the question is: am I approaching this the right way? It feels very smelly that I would have to write something to parse and transform the ODataQueryOptions into something EF can understand.

It seems this is a rather popular topic:

While most of these suggest using ProjectTo, none seem to address serialisation auto expanding properties, or how to handle expansion if ExplictExpansion has been configured.

Classes and Config below:

Entity Framework (V6.1.3) entities:

public class SalesOrderHeader
{
    public string SalesOrderNumber { get; set; }
    public string Alpha { get; set; }
    public string Customer { get; set; }
    public string Status { get; set; }
    public virtual ICollection<SalesOrderLine> SalesOrderLines { get; set; }
}

public class SalesOrderLine
{
    public string SalesOrderNumber { get; set; }
    public string OrderLineNumber { get; set; }        
    public string Product { get; set; }
    public string Description { get; set; }
    public decimal OrderQuantity { get; set; }

    public virtual SalesOrderHeader SalesOrderHeader { get; set; }
    public virtual MasterStockRecord MasterStockRecord { get; set; }
}

public class MasterStockRecord
{        
    public string ProductCode { get; set; }     
    public string Description { get; set; }
    public decimal Quantity { get; set; }
}

OData (V6.13.0) Data Transfer Objects:

public class SalesOrderDto
{
    [Key]
    public string SalesOrderNumber { get; set; }
    public string Customer { get; set; }
    public string Status { get; set; }
    public virtual ICollection<SalesOrderLineDto> SalesOrderLines { get; set; }
}

public class SalesOrderLineDto
{
    [Key]
    [ForeignKey("SalesOrderHeader")]
    public string SalesOrderNumber { get; set; }

    [Key]
    public string OrderLineNumber { get; set; }
    public string LineType { get; set; }
    public string Product { get; set; }
    public string Description { get; set; }
    public decimal OrderQuantity { get; set; }

    public virtual SalesOrderDto SalesOrderHeader { get; set; }
    public virtual StockDto MasterStockRecord { get; set; }
}

public class StockDto
{
    [Key]
    public string StockCode { get; set; }        
    public string Description { get; set; }        
    public decimal Quantity { get; set; }
}

OData Config:

var builder = new ODataConventionModelBuilder();

builder.EntitySet<StockDto>("Stock");
builder.EntitySet<SalesOrderDto>("SalesOrders");
builder.EntitySet<SalesOrderLineDto>("SalesOrderLines");

解决方案

I never really managed to work this one out. The ToNavigationPropertyArray() extension method helps a little, but does not handle infinite depth navigation.

The real solution is to create Actions or Functions to allow clients to request data requiring a more complicated query.

The other alternative is to make multiple smaller/simple calls then aggregate the data on the client, but this isn't really ideal.

这篇关于使用DTO与OData&amp; Web API的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆