I want to know if it's possible to pre-filter OData results in a WebAPI for items in the expand clause. I only want this to filter based on a predefined interface with a Deleted flag.
public interface IDbDeletedDateTime
{
DateTime? DeletedDateTime { get; set; }
}
public static class IDbDeletedDateTimeExtensions
{
public static IQueryable<T> FilterDeleted<T>(this IQueryable<T> self)
where T : IDbDeletedDateTime
{
return self.Where(s => s.DeletedDateTime == null);
}
}
public class Person : IDbDeletedDateTime
{
[Key]
public int PersonId { get; set }
public DateTime? DeletedDateTime { get; set; }
public virtual ICollection<Pet> Pets { get; set; }
}
public class Pet : IDbDeletedDateTime
{
[Key]
public int PetId { get; set }
public int PersonId { get; set }
public DateTime? DeletedDateTime { get; set; }
}
public class PersonController : ApiController
{
private PersonEntities db = new PersonEntities();
[EnableQuery]
// GET: api/Persons
public IQueryable<Person> GetPersons()
{
return db.Persons.FilterDeleted();
}
}
You can see that I'm very easily filtering deleted people. The problem comes when someone gets deleted Pets from a query like /api/Persons?$expand=Pets
Is there a way to check if this expansion of "Pets" is an IDbDeletedDateTime and filter them accordingly? Maybe there is a better way to approach this?
EDIT:
I tried to solve this based on what was picked up in this answer. I don't think it can be done, at least not in all scenarios. The only part of a ExpandedNavigationSelectItem
that even looks like it is related to the filters is the FilterClause
. This can be null when it has no filter, and it is only a getter property, meaning we can't set it with a new filter if we wanted to. Weather or not it is possible to modify a current filter is only covering a small use case that I'm not particularly interested in if I can't add a filter freshly.
I have an extension method that will recurse through all the expand clauses and you can at least see what the FilterOption is for each expansion. If anyone can get this 90% code fully realized, that would be amazing, but I'm not holding my breath on it.
public static void FilterDeletables(this ODataQueryOptions queryOptions)
{
//Define a recursive function here.
//I chose to do it this way as I didn't want a utility method for this functionality. Break it out at your discretion.
Action<SelectExpandClause> filterDeletablesRecursive = null;
filterDeletablesRecursive = (selectExpandClause) =>
{
//No clause? Skip.
if (selectExpandClause == null)
{
return;
}
foreach (var selectedItem in selectExpandClause.SelectedItems)
{
//We're only looking for the expanded navigation items.
var expandItem = (selectedItem as ExpandedNavigationSelectItem);
if (expandItem != null)
{
//https://msdn.microsoft.com/en-us/library/microsoft.data.odata.query.semanticast.expandednavigationselectitem.pathtonavigationproperty(v=vs.113).aspx
//The documentation states: "Gets the Path for this expand level. This path includes zero or more type segments followed by exactly one Navigation Property."
//Assuming the documentation is correct, we can assume there will always be one NavigationPropertySegment at the end that we can use.
var edmType = expandItem.PathToNavigationProperty.OfType<NavigationPropertySegment>().Last().EdmType;
string stringType = null;
IEdmCollectionType edmCollectionType = edmType as IEdmCollectionType;
if (edmCollectionType != null)
{
stringType = edmCollectionType.ElementType.Definition.FullTypeName();
}
else
{
IEdmEntityType edmEntityType = edmType as IEdmEntityType;
if (edmEntityType != null)
{
stringType = edmEntityType.FullTypeName();
}
}
if (!String.IsNullOrEmpty(stringType))
{
Type actualType = typeof(PetStoreEntities).Assembly.GetType(stringType);
if (actualType != null && typeof (IDbDeletable).IsAssignableFrom(actualType))
{
var filter = expandItem.FilterOption;
//expandItem.FilterOption = new FilterClause(new BinaryOperatorNode(BinaryOperatorKind.Equal, new , ));
}
}
filterDeletablesRecursive(expandItem.SelectAndExpand);
}
}
};
filterDeletablesRecursive(queryOptions.SelectExpand?.SelectExpandClause);
}