我目前正在asp.net中开展一个Web服务项目。我尝试在我的函数中包含子元素来检索数据。
它适用于网址:
http://localhost:8080/myEntity/?$expand=myChildElement
但我似乎无法像下面这样包含以下级别的子元素:
http://localhost:8080/myEntity/?$expand=myChildElement/mySubChildElement
这是我的功能:
public virtual async Task<IHttpActionResult> GetElements(ODataQueryOptions<TEntity> queryOptions)
{
IQueryable<TPoco> query = context.Set<TPoco>();
string[] includes = null;
string includeText = queryOptions.SelectExpand != null ? queryOptions.SelectExpand.RawExpand : null;
if(! string.IsNullOrEmpty(includeText))
{
includes = queryOptions.SelectExpand.RawExpand.Split(',');
return Ok(await query.ProjectTo<TEntity>(null, includes).ToListAsync());
}
return Ok(await query.ProjectTo<TEntity>().ToListAsync());
}
这是我的TPoco模型的一个例子(myChildElement可以是实体“Project”,mySubChildElement是子实体“ProjectType”):
public class ContractEntity : BaseEntity
{
public ContractEntity()
{
this.Addresses = new HashSet<AddressEntity>();
}
override public Guid Id { get; set; }
override public string DisplayName { get { return No.ToString(); } set { } }
override public string ClassType { get { return "Contract"; } set { } }
override public string MarkerColor { get { return Colors.Green.ToString(); } set { } }
public int? No { get; set; }
public DateTime? OfficialDate { get; set; }
public DateTime? ValidDate { get; set; }
public DateTime? SignatureDate { get; set; }
public Guid? ProjectId { get; set; }
[...]
public virtual ICollection<AccountingItemEntity> AccountingItems { get; set; }
public virtual ICollection<TagEntity> Tags { get; set; }
public virtual ProjectEntity Project { get; set; }
[...]
}
我希望你能帮助我。
我这样做的方式,我使用.ProjectTo
返回一个IQueryable
的事实,所以我让OData框架处理查询,不是自己调用ToListAsync()
,而是返回IQueryable
。
// In some class that is ': ODataController'
[EnableQuery] //Attribute makes sure the OData framework handles querying
public IQueryable<TDto> Get(ODataQueryOptions<TDto> options) {
//The names of the properties requested by the client in the '$expand=' query option
string[] requestedExpands = Helpers.Expand.GetMembersToExpandNames(options); // I made a helper for this, you can probably just use your code, or see my impementation below.
var set = Db.Set<TEntity>().AsNoTracking(); //You might want to remove AsNoTracking if it doesn't work
var converted = set.ProjectTo<TDto>(MapConfig, null, requestedExpands);
return converted;
}
在这个例子中,TDto
是我想发送给客户端的类型TEntity
是Entity Framework POCO类。
我在应用程序启动时设置了MapConfig
,在这里我将可扩展属性设置为'Explicit expand'模式:
MapConfig = new MapperConfiguration(cfg => {
cfg.CreateMap<EFType, DTOType>(MemberList.Destination)
.ForMember(c => c.ExpandableProperty, options => options.ExplicitExpansion());
});
正如我在评论中所说,我通过在网址中嵌套$expand=
来请求数据:
api/myEntity?$expand=myChildElement($expand=mySubChildElement)
这对我有用,希望你能复制那个成功。
编辑:再看一遍,如果你想扩展myEntity.myChildElement.mySubChildElement
你传递给AutoMapper的string[]
'requiredExpands'必须包含1个条目:myChildElement.mySubChildElement
。我有一个为所有3个实体定义的映射,再次使用ExplicitExpansion
选项。
根据@Tim Pohlmann的要求更新我对GetMembersToExpandNames
的实施:
public static class Expand {
public static string[] GetMembersToExpandNames(ODataQueryOptions options) {
return GetExpandPropertyPaths(GetExpandItems(options?.SelectExpand?.SelectExpandClause)).ToArray();
}
private static IEnumerable<string> GetExpandPropertyPaths(IEnumerable<ExpandedNavigationSelectItem> items, string prefix = "") {
foreach(var item in items) {
foreach(var res in GetExpandPropertyPaths(item, prefix)) {
yield return res;
}
}
}
private static IEnumerable<string> GetExpandPropertyPaths(ExpandedNavigationSelectItem item, string prefix = "") {
var curPropName = item.NavigationSource.Name;
var nestedExpand = GetExpandItems(item.SelectAndExpand).ToArray();
if(nestedExpand.Count() > 0) {
foreach(var res in GetExpandPropertyPaths(nestedExpand, $"{prefix}{curPropName}.")) {
yield return res;
}
} else {
yield return $"{prefix}{curPropName}";
}
}
private static IEnumerable<ExpandedNavigationSelectItem> GetExpandItems(SelectExpandClause sec) {
if(sec != null) {
var res = sec?.SelectedItems?.OfType<ExpandedNavigationSelectItem>();
if(res != null) {
return res;
}
}
return new ExpandedNavigationSelectItem[0];
}
}