使用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)
{
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)配置如下,请注意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());
ExplicitExpansion()
然后创建一个新问题,其中以下请求引发错误:
/ odatademo / SalesOrders表(&#39; 123456&#39;)?$扩大= SalesOrderLines
URI中指定的查询无效。指定的类型成员&#39; SalesOrderLines&#39; LINQ to Entities
不支持
导航属性SalesOrderLines
对于EF来说是未知的,因此这个错误几乎与我预期的一样。问题是,我该如何处理这类请求?
ProjectTo()
方法确实有一个重载,允许我传入一个需要扩展的属性数组,我发现&amp;修改了扩展方法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;
}
这适用于扩展,所以现在我可以处理如下的请求:
/ odatademo / SalesOrders表(&#39; 123456&#39;)?$扩大= SalesOrderLines
或更复杂的请求,如:
/ odatademo / SalesOrders表(&#39; 123456&#39;)?$扩大= SalesOrderLines($扩大= MasterStockRecord)
然而,尝试将$ select与$ expand结合起来的更复杂的请求将失败:
/ odatademo / SalesOrders表(&#39; 123456&#39;)?$扩大= 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; }
}
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; }
}
OData配置:
var builder = new ODataConventionModelBuilder();
builder.EntitySet<StockDto>("Stock");
builder.EntitySet<SalesOrderDto>("SalesOrders");
builder.EntitySet<SalesOrderLineDto>("SalesOrderLines");
答案 0 :(得分:1)
我创建了一个Automapper显式导航扩展实用程序函数,该函数可以与N-deph扩展一起使用。将它发布在这里,因为它可以帮助某人。
public List<string> ProcessExpands(IEnumerable<SelectItem> items, string parentNavPath="")
{
var expandedPropsList = new List<String>();
if (items == null) return expandedPropsList;
foreach (var selectItem in items)
{
if (selectItem is ExpandedNavigationSelectItem)
{
var expandItem = selectItem as ExpandedNavigationSelectItem;
var navProperty = expandItem.PathToNavigationProperty?.FirstSegment?.Identifier;
expandedPropsList.Add($"{parentNavPath}{navProperty}");
//go recursively to subproperties
var subExpandList = ProcessExpands(expandItem?.SelectAndExpand?.SelectedItems, $"{parentNavPath}{navProperty}.");
expandedPropsList = expandedPropsList.Concat(subExpandList).ToList();
}
}
return expandedPropsList;
}
您可以使用以下方式调用它:
var navExp = ProcessExpands(options?.SelectExpand?.SelectExpandClause?.SelectedItems)
它会返回一个包含["Parent" ,"Parent.Child"]
答案 1 :(得分:0)
我从未真正设法解决这个问题。 ToNavigationPropertyArray()
扩展方法略有帮助,但无法处理无限深度导航。
真正的解决方案是创建动作或函数,以允许客户端请求需要更复杂查询的数据。
另一种选择是进行多个较小/简单的调用然后在客户端上聚合数据,但这并不是很理想。
答案 2 :(得分:0)
如果您想在AutoMapper中为显式扩展标记某些内容,则还需要在调用ProjectTo<>()
时选择加入。
// map
cfg.CreateMap<SalesOrderHeader, SalesOrderDto>()
.ForMember(dest => dest.SalesOrderLines, opt => opt.ExplicitExpansion());
// updated controller
[EnableQuery]
public IQueryable<SalesOrderDto> Get()
{
return _dbContext.SalesOrders
.ProjectTo<SalesOrderDto>(
AutoMapperConfig.Config,
so => so.SalesOrderLines,
// ... additional opt-ins
);
}
虽然AutoMapper wiki确实说明了这一点,但这个示例可能会因为不包括配对的ExplicitExpansion()
电话而有点误导。
要控制在投影期间展开哪些成员,请在配置中设置ExplicitExpansion,然后传入要显式展开的成员: