Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EntitySetController $expand and $select not working

I'm trying to get pages of results for a table, using OData provided by an EntitySetController. However all of my requests for $select and $expand fail. Any ideas?

I'm using Entity Framework v6 and System.Web.Http.OData v5.

My entity:

public partial class Contact : BaseEntity
{
    [Column("cont_id"), Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [Column("cust_id"), EitherRequired("SupplierId", Exclusive = true)]
    public int? CustomerId { get; set; }

    public virtual Customer Customer { get; set; }

    [Column("supp_id")]
    public int? SupplierId { get; set; }

    public virtual Supplier Supplier { get; set; }

    [Column("cont_title"), Required(AllowEmptyStrings = true), StringLength(5)]
    public string Title { get; set; }

    [Column("cont_firstname"), Required(AllowEmptyStrings = true), StringLength(50)]
    public string FirstName { get; set; }

    [Column("cont_lastname"), Display(Name = "Last Name"), Required(AllowEmptyStrings = true), StringLength(50)]
    public string LastName { get; set; }

    [Column("cont_phone"), DataType(DataType.PhoneNumber), StringLength(20)]
    public string PhoneNumber { get; set; }
}

My Controller:

public abstract class RepositoryController<TEntity, TKey> : EntitySetController<TEntity, TKey>
    where TEntity : BaseEntity
{
    protected readonly IRepository<TEntity> repository;
    protected readonly Func<TEntity, TKey> keySelector;

    public RepositoryController(IRepository<TEntity> repository, Func<TEntity, TKey> keySelector)
        : base()
    {
        this.repository = repository;
        this.keySelector = keySelector;
    }

    // GET api/entity
    [HttpGet]
    [Queryable]
    public override IQueryable<TEntity> Get()
    {
        return repository.Get();
    }

    public override void Delete(TKey key)
    {
        repository.Delete(GetEntityByKey(key));
        UnitOfWork.Save();
    }

    protected override TEntity GetEntityByKey(TKey key)
    {
        return repository.Get(key);
    }

    protected override TKey GetKey(TEntity entity)
    {
        return keySelector(entity);
    }

    protected override TEntity CreateEntity(TEntity entity)
    {
        if (!ModelState.IsValid)
        {
            throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
        }

        var result = repository.Insert(entity);
        UnitOfWork.Save();
        return result;
    }

    protected override TEntity UpdateEntity(TKey key, TEntity update)
    {
        if (!ModelState.IsValid)
        {
            throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
        }

        repository.Update(update, key);
        UnitOfWork.Save();
        return update;
    }
}

My request: http://localhost:60642/odata/Contact?$inlinecount=allpages&$top=20&$expand=Customer,Supplier

The result:

{
  "odata.error":{
    "code":"","message":{
      "lang":"en-US","value":"The query specified in the URI is not valid."
    },"innererror":{
      "message":"Could not find a property named 'Customer' on type 'System.Web.Http.OData.Query.Expressions.SelectAllAndExpand_1OfContact'.","type":"Microsoft.Data.OData.ODataException","stacktrace":"   at Microsoft.Data.OData.Query.SyntacticAst.ExpandBinder.GenerateExpandItem(ExpandTermToken tokenIn)\r\n   at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()\r\n   at System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext()\r\n   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)\r\n   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)\r\n   at Microsoft.Data.OData.Query.SyntacticAst.ExpandBinder.Bind(ExpandToken tokenIn)\r\n   at Microsoft.Data.OData.Query.SelectExpandSemanticBinder.Parse(IEdmEntityType elementType, IEdmEntitySet entitySet, ExpandToken expandToken, SelectToken selectToken, ODataUriParserConfiguration configuration)\r\n   at Microsoft.Data.OData.Query.ODataUriParser.ParseSelectAndExpandImplementation(String select, String expand, IEdmEntityType elementType, IEdmEntitySet entitySet)\r\n   at Microsoft.Data.OData.Query.ODataUriParser.ParseSelectAndExpand(String select, String expand, IEdmEntityType elementType, IEdmEntitySet entitySet)\r\n   at System.Web.Http.OData.Query.SelectExpandQueryOption.get_SelectExpandClause()\r\n   at System.Web.Http.OData.Query.Validators.SelectExpandQueryValidator.Validate(SelectExpandQueryOption selectExpandQueryOption, ODataValidationSettings validationSettings)\r\n   at System.Web.Http.OData.Query.SelectExpandQueryOption.Validate(ODataValidationSettings validationSettings)\r\n   at System.Web.Http.OData.Query.Validators.ODataQueryValidator.Validate(ODataQueryOptions options, ODataValidationSettings validationSettings)\r\n   at System.Web.Http.OData.Query.ODataQueryOptions.Validate(ODataValidationSettings validationSettings)\r\n   at System.Web.Http.QueryableAttribute.ValidateQuery(HttpRequestMessage request, ODataQueryOptions queryOptions)\r\n   at System.Web.Http.QueryableAttribute.ExecuteQuery(Object response, HttpRequestMessage request, HttpActionDescriptor actionDescriptor)\r\n   at System.Web.Http.QueryableAttribute.OnActionExecuted(HttpActionExecutedContext actionExecutedContext)"
    }
  }
}

But I see it on $metadata:

<EntityType Name="Contact">
    <Key>
        <PropertyRef Name="Id"/>
    </Key>
    <Property Name="Id" Type="Edm.Int32" Nullable="false"/>
    <Property Name="CustomerId" Type="Edm.Int32"/>
    <Property Name="SupplierId" Type="Edm.Int32"/>
    <Property Name="Title" Type="Edm.String" Nullable="false"/>
    <Property Name="FirstName" Type="Edm.String" Nullable="false"/>
    <Property Name="LastName" Type="Edm.String" Nullable="false"/>
    <Property Name="PhoneNumber" Type="Edm.String"/>
    <NavigationProperty Name="Customer" Relationship="Vantage.Data.Core.Entities.Vantage_Data_Core_Entities_Contact_Customer_Vantage_Data_Core_Entities_Customer_CustomerPartner" ToRole="Customer" FromRole="CustomerPartner"/>
    <NavigationProperty Name="Supplier" Relationship="Vantage.Data.Core.Entities.Vantage_Data_Core_Entities_Contact_Supplier_Vantage_Data_Core_Entities_Supplier_SupplierPartner" ToRole="Supplier" FromRole="SupplierPartner"/>
</EntityType>

I get a similar exception for selects. What' wrong?

Edit: Update. I get a different error if I try to filter something on the root entity while also expanding. Seems the entity truly is screwed.

http://localhost:60642/odata/Contact?$top=20&$inlinecount=allpages&$filter=substringof('ORDERS',%20LastName)%20eq%20true&$expand=Customer,Supplier

{
  "odata.error":{
    "code":"","message":{
      "lang":"en-US","value":"The query specified in the URI is not valid. Could not find a property named 'LastName' on type 'System.Web.Http.OData.Query.Expressions.SelectAllAndExpand_1OfContact'."
    },"innererror":{
      "message":"Could not find a property named 'LastName' on type 'System.Web.Http.OData.Query.Expressions.SelectAllAndExpand_1OfContact'.","type":"Microsoft.Data.OData.ODataException","stacktrace":"   at Microsoft.Data.OData.Query.EndPathBinder.GeneratePropertyAccessQueryForOpenType(EndPathToken endPathToken, SingleValueNode parentNode)\r\n   at Microsoft.Data.OData.Query.EndPathBinder.BindEndPath(EndPathToken endPathToken, BindingState state)\r\n   at Microsoft.Data.OData.Query.MetadataBinder.BindEndPath(EndPathToken endPathToken)\r\n   at Microsoft.Data.OData.Query.MetadataBinder.Bind(QueryToken token)\r\n   at Microsoft.Data.OData.Query.MetadataBinder.BindFunctionParameter(FunctionParameterToken token)\r\n   at Microsoft.Data.OData.Query.MetadataBinder.Bind(QueryToken token)\r\n   at Microsoft.Data.OData.Query.FunctionCallBinder.<BindFunctionCall>b__6(FunctionParameterToken ar)\r\n   at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()\r\n   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)\r\n   at Microsoft.Data.OData.Query.FunctionCallBinder.BindFunctionCall(FunctionCallToken functionCallToken, BindingState state)\r\n   at Microsoft.Data.OData.Query.MetadataBinder.BindFunctionCall(FunctionCallToken functionCallToken)\r\n   at Microsoft.Data.OData.Query.MetadataBinder.Bind(QueryToken token)\r\n   at Microsoft.Data.OData.Query.BinaryOperatorBinder.GetOperandFromToken(BinaryOperatorKind operatorKind, QueryToken queryToken)\r\n   at Microsoft.Data.OData.Query.BinaryOperatorBinder.BindBinaryOperator(BinaryOperatorToken binaryOperatorToken)\r\n   at Microsoft.Data.OData.Query.MetadataBinder.BindBinaryOperator(BinaryOperatorToken binaryOperatorToken)\r\n   at Microsoft.Data.OData.Query.MetadataBinder.Bind(QueryToken token)\r\n   at Microsoft.Data.OData.Query.FilterBinder.BindFilter(QueryToken filter)\r\n   at Microsoft.Data.OData.Query.ODataUriParser.ParseFilterImplementation(String filter, IEdmType elementType, IEdmEntitySet entitySet)\r\n   at System.Web.Http.OData.Query.FilterQueryOption.get_FilterClause()\r\n   at System.Web.Http.OData.Query.Validators.FilterQueryValidator.Validate(FilterQueryOption filterQueryOption, ODataValidationSettings settings)\r\n   at System.Web.Http.OData.Query.FilterQueryOption.Validate(ODataValidationSettings validationSettings)\r\n   at System.Web.Http.OData.Query.Validators.ODataQueryValidator.Validate(ODataQueryOptions options, ODataValidationSettings validationSettings)\r\n   at System.Web.Http.OData.Query.ODataQueryOptions.Validate(ODataValidationSettings validationSettings)\r\n   at System.Web.Http.QueryableAttribute.ValidateQuery(HttpRequestMessage request, ODataQueryOptions queryOptions)\r\n   at System.Web.Http.QueryableAttribute.ExecuteQuery(Object response, HttpRequestMessage request, HttpActionDescriptor actionDescriptor)\r\n   at System.Web.Http.QueryableAttribute.OnActionExecuted(HttpActionExecutedContext actionExecutedContext)"
    }
  }
}
like image 731
Tim Avatar asked Dec 06 '13 12:12

Tim


2 Answers

I came across a similar issue in OData V4. In this case it turned out if you used an attribute on the Get method and registered another attribute in your config, it errors because you are calling the EnableQuery code twice:

[EnableQuery]
public IQueryable<RegionEntity> Get()
{
    var result = _regionService.GetAll();
    return result;
}

and in the configuration

config.AddODataQueryFilter(new SecureAccessAttribute());

As the SecureAccessAttribute extended EnableQueryAttribute the first call of Validate worked fine, the second could not find the property, so although it doesn't seem to be documented, ensure not to call this twice for one query!

like image 53
CodePB Avatar answered Nov 15 '22 04:11

CodePB


If you want to use your own attribute you need to register it!

Just call config.EnableQuerySupport(new ControlledQueryableAttribute());

like image 23
Roald Avatar answered Nov 15 '22 04:11

Roald