简体   繁体   中英

Returning derived types in OData

Here is my code using ODatav4:

WebApiConfig.cs

ODataConventionModelBuilder builder = new ODataConventionModelBuilder();

builder.EntitySet<PurchaseBaseDTO>("Purchases");
builder.EntityType<PurchaseBaseDTO>().Filter();
builder.EntityType<PurchaseBaseDTO>().Expand();
builder.EntityType<PurchaseBaseDTO>().OrderBy();
builder.EntityType<PurchaseBaseDTO>().Count();
builder.EntityType<PurchaseBaseDTO>().Page(50, 999999);

config.MapODataServiceRoute(
            routeName: "myRoute",
            routePrefix: "api",
            model: builder.GetEdmModel()
            );

FluentValidationModelValidatorProvider.Configure(config);

PurchasesController.cs

public class PurchasesController : ODataController
{
    readonly PurchaseService pgs = new PurchaseService();

    [EnableQuery]
    public IQueryable<PurchaseSimpleDTO> Get()
    {
        return pgs.Get();
    }

    [EnableQuery]
    public SingleResult<PurchaseComplexDTO> Get([FromODataUri] int key)
    {
        return SingleResult.Create(pgs.Get(key));
    }
}

PurchaseBaseDTO.cs

public abstract class PurchaseGoodsBaseDTO
{
    public int Id { get; set; }
}

PurchaseSimpleDTO.cs

public class PurchaseGoodsSimpleDTO : PurchaseGoodsBaseDTO
{
    [AutoExpand]
    public ActionTaker Supplier { get; set; }
}

ActionTaker.cs

public class ActionTaker
{
    public int Id { get; set; }
    public string Name { get; set; }
}

PurchaseComplexDTO.cs

public class PurchaseGoodsComplexDTO : PurchaseGoodsBaseDTO
{
    public decimal AmountPurchased { get; set; }
    public string PurchaseUnit { get; set; }
    public decimal Price { get; set; }

    public string CreatedBy { get; set; }
    public DateTime CreatedAt { get; set; }
    public string ModifiedBy { get; set; }
    public DateTime? ModifiedAt { get; set; }
}

In PurchaseService I simply create those objects and assign fields values. I return IQueryable<T> in both methods with respective Simple or Complex version of DTO.

Running http://localhost:64502/api/Purchases?$top=10&$skip=0&$count=true returns list of purchases from database, everything is OK.

Running http://localhost:64502/api/Purchases(1) returns

"error": {
    "code": "",
    "message": "The query specified in the URI is not valid. Encountered invalid type cast. 'PurchaseSimpleDTO' is not assignable from 'PurchaseComplexDTO'.",
    "innererror": {
        "message": "Encountered invalid type cast. 'PurchaseSimpleDTO' is not assignable from 'PurchaseComplexDTO'.",
        "type": "Microsoft.OData.ODataException",
        "stacktrace": "   w Microsoft.OData.UriParser.UriEdmHelpers.CheckRelatedTo(IEdmType parentType, IEdmType childType)\r\n   w Microsoft.OData.UriParser.SelectExpandPathBinder.FollowTypeSegments(PathSegmentToken firstTypeToken, IEdmModel model, Int32 maxDepth, ODataUriResolver resolver, IEdmStructuredType& currentLevelType, PathSegmentToken& firstNonTypeToken)\r\n   w Microsoft.OData.UriParser.SelectExpandBinder.GenerateExpandItem(ExpandTermToken tokenIn)\r\n   w System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()\r\n   w System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext()\r\n   w System.Collections.Generic.List`1.InsertRange(Int32 index, IEnumerable`1 collection)\r\n   w Microsoft.OData.UriParser.SelectExpandBinder.Bind(ExpandToken expandToken, SelectToken selectToken)\r\n   w Microsoft.OData.UriParser.SelectExpandSemanticBinder.Bind(ODataPathInfo odataPathInfo, ExpandToken expandToken, SelectToken selectToken, ODataUriParserConfiguration configuration, BindingState state)\r\n   w Microsoft.OData.UriParser.ODataQueryOptionParser.ParseSelectAndExpandImplementation(String select, String expand, ODataUriParserConfiguration configuration, ODataPathInfo odataPathInfo)\r\n   w Microsoft.OData.UriParser.ODataQueryOptionParser.ParseSelectAndExpand()\r\n   w Microsoft.AspNet.OData.Query.SelectExpandQueryOption.ProcessLevels()\r\n   w Microsoft.AspNet.OData.Query.SelectExpandQueryOption.get_ProcessedSelectExpandClause()\r\n   w Microsoft.AspNet.OData.Query.ODataQueryOptions.ApplySelectExpand[T](T entity, ODataQuerySettings querySettings)\r\n   w Microsoft.AspNet.OData.Query.ODataQueryOptions.ApplyTo(IQueryable query, ODataQuerySettings querySettings)\r\n   w Microsoft.AspNet.OData.EnableQueryAttribute.ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions)\r\n   w Microsoft.AspNet.OData.EnableQueryAttribute.ExecuteQuery(Object responseValue, IQueryable singleResultCollection, IWebApiActionDescriptor actionDescriptor, Func`2 modelFunction, IWebApiRequestMessage request, Func`2 createQueryOptionFunction)\r\n   w Microsoft.AspNet.OData.EnableQueryAttribute.OnActionExecuted(Object responseValue, IQueryable singleResultCollection, IWebApiActionDescriptor actionDescriptor, IWebApiRequestMessage request, Func`2 modelFunction, Func`2 createQueryOptionFunction, Action`1 createResponseAction, Action`3 createErrorAction)"
    }
}

What I already tried:

I analyzed this (among many other less relevant) thread and I can't find many differences, at least none important, if I'm correct.

I tried to return PurchaseComplexDTO instead of SingleResult<PurchaseComplexDTO> .

I tried to return IQueryable<PurchaseComplexDTO> instead of SingleResult<PurchaseComplexDTO> .

I tried to change return typed of both methods in controller to PurchaseBaseDTO .

Nothing helped. Where is my mistake?

This is only speculation, but have you tried to use Attribute Routing? The problem here is that your EdmModel is specifying the base type, but you do not have a corresponding Get method that explicitly matches the convention of having a return type of PurchaseBaseDTO (or IHttpActionResult), a name matching Get and that has a parameter called key

public class PurchasesController : ODataController
{
    readonly PurchaseService pgs = new PurchaseService();

    [EnableQuery]
    public IQueryable<PurchaseSimpleDTO> Get()
    {
        return pgs.Get();
    }

    [ODataRoute("({key})")]
    [EnableQuery]
    public SingleResult<PurchaseComplexDTO> Get([FromODataUri] int key)
    {
        return SingleResult.Create(pgs.Get(key));
    }
}

You will also have to enable attribute routing:

string routePrefix = "api";
var uriConventions = ODataRoutingConventions.CreateDefaultWithAttributeRouting(routePrefix, config);
config.MapODataServiceRoute(
        routeName: "myRoute",
        routePrefix: routePrefix,
        model: builder.GetEdmModel(),
        pathHandler: new DefaultODataPathHandler(),
        routingConventions: uriConventions 
        );config.MapODataServiceRoute("myRoute", routePrefix, model, new DefaultODataPathHandler(), uriConventions);

I changed my second Get method and now it works:

[EnableQuery]
public PurchaseGoodsComplexDTO Get([FromODataUri] int key)
{
    return pgs.Get(key).FirstOrDefault();
}

pgs.Get(key); returns an IQueryable .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM