将DTO与OData和Web API结合使用

phi*_*eed 10 asp.net entity-framework automapper odata asp.net-web-api

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

我使用AutoMapper将EF实体转换为其DTO计数器部件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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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());
Run Code Online (Sandbox Code Playgroud)

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

/ odatademo/SalesOrders表( '123456')?$扩大= SalesOrderLines

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

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

ProjectTo()方法确实有一个重载,允许我传入需要扩展的属性数组,我找到并修改了扩展方法ToNavigationPropertyArray,尝试将请求解析为字符串数组:

[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;
}
Run Code Online (Sandbox Code Playgroud)

这适用于扩展,所以现在我可以处理如下的请求:

/ odatademo/SalesOrders表( '123456')?$扩大= SalesOrderLines

或者更复杂的请求,例如:

/ odatademo/SalesOrders表( '123456')?$扩大= SalesOrderLines($扩大= MasterStockRecord)

但是,尝试将$ select与$ expand结合使用的更复杂的请求将失败:

/ odatademo/SalesOrders表( '123456')?$扩大= SalesOrderLines($选择= OrderQuantity)

序列不包含任何元素

所以,问题是:我是以正确的方式接近这个吗?感觉非常臭我必须写一些东西来解析并将ODataQueryOptions转换成EF可以理解的东西.

这似乎是一个相当热门的话题:

虽然大多数建议使用ProjectTo,但似乎没有解决序列化自动扩展属性,或如果ExplictExpansion已配置如何处理扩展.

下面的类和配置:

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

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; }
}
Run Code Online (Sandbox Code Playgroud)

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

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; }
}
Run Code Online (Sandbox Code Playgroud)

OData配置:

var builder = new ODataConventionModelBuilder();

builder.EntitySet<StockDto>("Stock");
builder.EntitySet<SalesOrderDto>("SalesOrders");
builder.EntitySet<SalesOrderLineDto>("SalesOrderLines");
Run Code Online (Sandbox Code Playgroud)

phi*_*eed 1

我从来没有真正解决过这个问题。扩展ToNavigationPropertyArray()方法有一点帮助,但不能处理无限深度导航。

真正的解决方案是创建操作或函数以允许客户端请求需要更复杂查询的数据。

另一种选择是进行多个较小/简单的调用,然后聚合客户端上的数据,但这并不理想。


归档时间:

查看次数:

3041 次

最近记录:

6 年,4 月 前