I'm currently working on a web service project in asp.net. And I try to include child elements in my function that retrieves the data.
It works with the url :
http://localhost:8080/myEntity/?$expand=myChildElement
But I can't seem to include the child elements of a level below like this :
http://localhost:8080/myEntity/?$expand=myChildElement/mySubChildElement
Here is my function :
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());
}
And here is an example of my TPoco model (myChildElement can be the entity "Project" and mySubChildElement the child entity "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; }
[...]
}
I hope you can help me.
The way I do this, I use the fact that .ProjectTo
returns an IQueryable
so I let the OData framework handle querying by not calling ToListAsync()
myself, but returning the 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;
}
In this example TDto
is the type I want to sent to the client TEntity
is the Entity Framework POCO class.
My MapConfig
I setup on application startup, here I set expandable properties to 'Explicit expand' mode:
MapConfig = new MapperConfiguration(cfg => {
cfg.CreateMap<EFType, DTOType>(MemberList.Destination)
.ForMember(c => c.ExpandableProperty, options => options.ExplicitExpansion());
});
As I said in comments, I request the data by nesting $expand=
in the URL:
api/myEntity?$expand=myChildElement($expan‌​d=mySubChildElement)
This works for me, hope you can replicate that success.
EDIT: Looked at it again, if you want to expand myEntity.myChildElement.mySubChildElement
the string[]
'requestedExpands' you pass to AutoMapper must contain 1 entry: myChildElement.mySubChildElement
. And I have a mapping defined for all 3 entities, again with the ExplicitExpansion
option.
Update, as requested by @Tim Pohlmann, my implementation of 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];
}
}