Hinzufügen einer benutzerdefinierten abfragegestützten Navigationseigenschaft zu ODataConventionModelBuilder

Lage

Ich habe die folgenden Modellklassen erstellt

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

    public virtual ICollection<PartState> PartStates {get;set; }
}

public class PartState
{
    public int Id {get;set;}
    public string State {get;set;}

    public int CarId {get;set;}
    public virtual Car Car {get;set;}

    public int PartId {get;set;}
    public virtual Part Part {get;set;}
}

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

Und ein passender DbContext

public class CarContext : DbContext
{
    public DbSet<Car> Cars {get;set;}
    public DbSet<PartState> PartStates {get;set;}
    public DbSet<Part> Parts {get;set;}
}

Und eine WebApplication erstellt, um diese über odata mithilfe der Gerüstvorlage "Web API 2 OData Controller with Actions, using Entity Framework" verfügbar zu machen.

außerdem erstelle ich folgende webapi config:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        var builder = new ODataConventionModelBuilder();
        builder.EntitySet<Car>("Cars");
        builder.EntitySet<PartState>("PartStates");
        builder.EntitySet<Part>("Parts");
        var edmModel = builder.GetEdmModel();
        config.Routes.MapODataRoute("odata", "odata", edmModel);
    }
}

Jetzt möchte ich meinem Cars Controller die folgende Methode hinzufügen

// GET: odata/Cars(5)/Parts
[Queryable]
public IQueryable<Part> GetParts([FromODataUri] int key)
{
    var parts = db.PartStates.Where(s => s.CarId == key).Select(s => s.Part).Distinct();
    return parts;
}

Und rufen Sie die Daten mit dieser URL ab:

http://localhost/odata/Cars(1)/Parts

Aber es funktioniert nicht, stattdessen erhalte ich den folgenden Fehler:

{
  "odata.error":{
    "code":"","message":{
      "lang":"en-US","value":"No HTTP resource was found that matches the request URI 'http://localhost/odata/Cars(1)/Parts'."
    },"innererror":{
      "message":"No routing convention was found to select an action for the OData path with template '~/entityset/key/unresolved'.","type":"","stacktrace":""
    }
  }
}
Frage

Also meine Frage ist, ist das überhaupt möglich ?!

Ich habe versucht, eine Navigation-Eigenschaft manuell zu erstellen und sie dem EDM-Modell hinzuzufügen. Dadurch wird zwar das Problem behoben, die neue Methode aufzurufen, es treten jedoch auch neue Fehler auf.

BEARBEITEN:

Welche ID hat versucht, es manuell auf diese Weise hinzuzufügen:

var edmModel = (EdmModel)builder.GetEdmModel();
var carType = (EdmEntityType)edmModel.FindDeclaredType("Car");
var partType = (EdmEntityType)edmModel.FindDeclaredType("Part");

var partsProperty = new EdmNavigationPropertyInfo();
partsProperty.TargetMultiplicity = EdmMultiplicity.Many;
partsProperty.Target = partType;
partsProperty.ContainsTarget = false;
partsProperty.OnDelete = EdmOnDeleteAction.None;
partsProperty.Name = "Parts";

var carsProperty = new EdmNavigationPropertyInfo();
carsProperty.TargetMultiplicity = EdmMultiplicity.Many;
carsProperty.Target = carType;
carsProperty.ContainsTarget = false;
carsProperty.OnDelete = EdmOnDeleteAction.None;
carsProperty.Name = "Cars";

var nav = EdmNavigationProperty.CreateNavigationPropertyWithPartner(partsProperty, carsProperty);

carType.AddProperty(nav);

config.Routes.MapODataRoute("odata", "odata", edmModel);

Dadurch konnte ich zwar die oben angegebene Methode über die ebenfalls oben angegebene URL aufrufen, es wurde jedoch der folgende Fehler ausgegeben:

{
  "odata.error":{
    "code":"","message":{
      "lang":"en-US","value":"An error has occurred."
    },"innererror":{
      "message":"The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; odata=fullmetadata; charset=utf-8'.","type":"System.InvalidOperationException","stacktrace":"","internalexception":{
        "message":"The related entity set could not be found from the OData path. The related entity set is required to serialize the payload.","type":"System.Runtime.Serialization.SerializationException","stacktrace":"   at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteObject(Object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)\r\n   at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content, HttpContentHeaders contentHeaders)\r\n   at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()\r\n   at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__1b.MoveNext()"
      }
    }
  }
}

Antworten auf die Frage(3)

Ihre Antwort auf die Frage