Search code examples
c#asp.net-coreodata

OData filtering on client-defined dynamic properties


I have two entities defined in the server:

public partial class MyModel 
{
    public string ModelCode { get; set; }
    public virtual ICollection<MyModelSubItem> SubItems { get; set; } = new HashSet<MyModelSubItem>();
    [NotMapped]
    public IDictionary<string, object> DynamicProperties { get; set; }
}

public partial class MyModelSubItem
{
    public string SubItemCode { get; set; }
}

The controller has the following:

[EnableQuery]
public IQueryable<MyModel> Get()
{
    return _myModelContext.GetAll();
}

In the client, I extend MyModel:

public partial class MyModel 
{
    public int SubItemCount => SubItems.Count;
}

When I try to query the DB using the client-defined SubItemCount property, I get the following error

Query: /odata/v1/MyModel?$filter=true&$orderby=SubItemCount%20desc

Error: System.ArgumentException: Static property requires null instance, non-static property requires non-null instance. (Parameter 'instance')

Querying using the non-dynamic properties work fine. Even adding/patching work fine when requests include the dynamic properties. It's only filtering/sorting that isn't working.

I am using OData v4 and the Unchase OData client code generator.

I was wondering why this is case. Any help would be highly appreciated.


Solution

  • I solved the issue by using a custom EnableQuery attribute on the controller, as per the answer posted here:

    .NET Core 2.1 OData v4 modify $filter on server side

    public class MyModelEnableQueryAttribute : EnableQueryAttribute
    {
        public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions)
        {
            if (queryOptions.Filter != null)
            {
                var queryDict = new Dictionary<string, StringValues>();
                foreach (var queryKeyValPair in queryOptions.Request.Query)
                {
                    var modifiedSingleQueryValues = new List<string>();
                    foreach (var singleQueryValues in queryKeyValPair.Value)
                    {
                        if (singleQueryValues.Contains("SubItemCount"))
                        {
                            modifiedSingleQueryValues.Add(singleQueryValues.Replace("SubItemCount", "SubItems/$count"));
                        }
                        else
                        {
                            modifiedSingleQueryValues.Add(singleQueryValues);
                        }
                    }
    
                    queryDict.Add(queryKeyValPair.Key, new StringValues(modifiedSingleQueryValues.ToArray()));
                }
    
                queryOptions.Request.Query = new QueryCollection(queryDict);
                queryOptions = new ODataQueryOptions(queryOptions.Context, queryOptions.Request);
            }
    
            return base.ApplyQuery(queryable, queryOptions);
        }
    }
    

    Controller:

    [MyModelEnableQuery]
    public IQueryable<MyModel> Get()
    {
        return _myModelContext.GetAll();
    }
    

    This requires that the request includes $expand and $count on SubItems e.g.

    /odata/v1/MyModel?$filter=true&$orderby=SubItemCount%20desc&$expand=SubItems($count=true)
    

    Hopefully someone finds this useful if they come across the same problem as I did.