WebAPI OData pre-filtering expands queries

I want to know if it is possible to pre-filter OData results in WebAPI for elements in an expand clause. I just want this to be based on a predefined interface with the 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 very easily filter remote people. The problem occurs when someone removes pets from a request like / api / Person? $ Expand = Pets

Is there a way to check if this Pets extension is IDbDeletedDateTime and filter them accordingly? Maybe there is a better way to approach this?

EDIT:

, . , , , . ExpandedNavigationSelectItem, , -, , FilterClause. null, , getter, , . , , , , .

, , , FilterOption . - 90% - , , .

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);
}
+3
4

, : , IDbDeletedDateTime, , , , ,

OData [EnableQuery], OData , , .

[MyEnableQuery] ApplyQuery: , $expand, , , IDbDeletedDateTime .

[EnableQuery] , ApplyQuery ODataQueryOptions, , (WebApi URI).

, , . , [EnableQuery] : ODataQueryOptions .

- :

// GET: api/Persons
public IQueryable<Person> GetPersons(ODataQueryOptions queryOptions)
{
    // Inspect queryOptions and apply the query options as you want
    // ...
    return db.Persons.FilterDeleted();
}

. " " , , . , , [Queryable] [EnableQuery], OData.

, , , ;).


EDIT: $expand:

OData V4 . , filer expand, - : GET api/user()? $Expand = followers ($ top = 2; $select = gender). OData ODataQueryOptions: , :

if (queryOptions.SelectExpand != null) {
    foreach (SelectItem item in queryOptions.SelectExpand.SelectExpandClause.SelectedItems) {
        if (item.GetType() == typeof(ExpandedNavigationSelectItem)) {
            ExpandedNavigationSelectItem navigationProperty =  (ExpandedNavigationSelectItem)item;

            // Get the name of the property expanded (this way you can control which navigation property you are about to expand)
            var propertyName = (navigationProperty.PathToNavigationProperty.FirstSegment as NavigationPropertySegment).NavigationProperty.Name.ToLowerInvariant();

            // Get skip and top nested filters:
            var skip = navigationProperty.SkipOption;
            var top = navigationProperty.TopOption;

            /* Here you should retrieve from your DB the entities that you
               will return as a result of the requested expand clause with nested filters
               ... */
            }
        }
    }
+5

, , , , ODataUri . , OData.

OData v4 $expand, filterOption - , . filterOption .

, (root ) , $filter, ODataUri.

OData Url:

/RootEntity?$expand=OtherEntity($expand=SomeOtherEntity)

URL- OData , :

/RootEntity?$filter=OtherEntity/SomeOtherEntity/Id eq 3&$expand=OtherEntity($expand=SomeOtherEntity)

:

  • ODataUriParser Url Uri

. :

var parser = new ODataUriParser(model, new Uri(serviceRootPath), requestUri);   
var odataUri = parser.ParseUri();
  1. , ODataUri ref ( , )

.

AddCustomFilters(ref ODataUri odataUri);

AddCustomFilters AddCustomFiltersToExpandedEntity, , .

foreach (var item in odatauri.SelectAndExpand.SelectedItems)
{
    AddCustomFiltersToExpandedEntity(ref ODataUri odataUri, ExpandedNavigationSelectItem expandedNavigationSelectItem, string parentNavigationNameProperty)
}

AddCustomFiltersToExpandedEntity , .

. $ ODataUri , .

odataUri.Filter = new FilterClause(newFilterExpression, newFilterRange);

. BinaryOperatorKind.And, , ODataUri

var combinedFilterExpression = new BinaryOperatorNode(BinaryOperatorKind.And, odataUri.Filter.Expression, newFilterExpression);
odataUri.Filter = new FilterClause(combinedFilterExpression, newFilterRange);
  1. ODataUriBuilder Url Uri

. :

var updatedODataUri = new Microsoft.OData.Core.UriBuilder.ODataUriBuilder(ODataUrlConventions.Default, odataUri).BuildUri();
  1. Uri Uri.

OData OData Url, , .

ActionContext.Request.RequestUri = updatedODataUri;

, , 100% , OData Url.

, .

+3

, , Entity Framework.

, , :

DbContext OnModelCreating

modelBuilder.Filter("NotDeleted", (Pet p) => p.Deleted, false);

, , , OData $expand. , , , - Dynamic Filters.

+2

OData , , . , , , . , - .

... , OData , , , RESTier Microsoft. , OData, , .

, Domain, :

private IQueryable<Pet> OnFilterPets(IQueryable<Pet> pets)
{
    return pets.Where(c => c.DeletedDateTime == null);
}

, , .

I could never implement this solution to know if it is worth it. There were too many problems to justify the value in my particular use case. This may well be a great solution for new projects or people who really need these features, but my specific use case was difficult for embedding the structure in existing logic.

Your mileage may vary, and this may be a useful basis for verification.

+1
source

All Articles