Aggiunta di una proprietà di navigazione personalizzata protetta da query a ODataConventionModelBuilder

Situazione

Ho creato le seguenti classi di modelli

public class Car { public int Id {get;set;} public string Name {get;set;} public virtual ICollection 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;} } 

E un DbContext corrispondente

 public class CarContext : DbContext { public DbSet Cars {get;set;} public DbSet PartStates {get;set;} public DbSet Parts {get;set;} } 

E ha creato una WebApplication per renderla disponibile via odata, usando il modello di scaffold “Web API 2 Controller OData con azioni, utilizzando Entity Framework”

inoltre creo i seguenti webapi config:

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

Ora voglio aggiungere il seguente metodo al mio controller per auto

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

E recupera i dati con questo Url:

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

Ma non funziona, invece ottengo il seguente errore:

 { "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":"" } } } 

Domanda

Quindi la mia domanda è, è ansible ?!

Ho provato a creare manualmente una proprietà Navigation e l’ho aggiunta al modello edm, mentre questo risolve il problema per richiamare il nuovo metodo, ma introduce anche nuovi errori.

MODIFICARE:

Quale ID ha provato ad aggiungerlo manualmente in questo modo:

 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); 

mentre questo mi ha permesso di invocare il metodo speciefied sopra attraverso l’URL anche sopra specificato, mi ha dato il seguente errore:

 { "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.d__1b.MoveNext()" } } } } 

Devi chiamare “AddNavigationTarget” su EntitySet. Supponi che il tuo spazio dei nomi sia “MyNamespace”, quindi aggiungi il seguente codice al tuo WebApiConfig.cs. In questo modo, il recupero dei dati con “Get: odata / Cars (1) / Parts” funzionerà.

  var cars = (EdmEntitySet)edmModel.EntityContainers().Single().FindEntitySet("Cars"); var parts = (EdmEntitySet)edmModel.EntityContainers().Single().FindEntitySet("Parts"); var carType = (EdmEntityType)edmModel.FindDeclaredType("MyNamespace.Car"); var partType = (EdmEntityType)edmModel.FindDeclaredType("MyNamespace.Part"); var partsProperty = new EdmNavigationPropertyInfo(); partsProperty.TargetMultiplicity = EdmMultiplicity.Many; partsProperty.Target = partType; partsProperty.ContainsTarget = false; partsProperty.OnDelete = EdmOnDeleteAction.None; partsProperty.Name = "Parts"; cars.AddNavigationTarget(carType.AddUnidirectionalNavigation(partsProperty), parts); 

Prendendo ulteriormente la risposta di @ FengZhao, al fine di ottenere l’url odata / Automobili che lavorano è necessario registrare anche il generatore di link di proprietà di navigazione per il generatore di link set di entity framework.

 var cars = (EdmEntitySet)edmModel.EntityContainers().Single().FindEntitySet("Cars"); var parts = (EdmEntitySet)edmModel.EntityContainers().Single().FindEntitySet("Parts"); var carType = (EdmEntityType)edmModel.FindDeclaredType("MyNamespace.Car"); var partType = (EdmEntityType)edmModel.FindDeclaredType("MyNamespace.Part"); var partsProperty = new EdmNavigationPropertyInfo(); partsProperty.TargetMultiplicity = EdmMultiplicity.Many; partsProperty.Target = partType; partsProperty.ContainsTarget = false; partsProperty.OnDelete = EdmOnDeleteAction.None; partsProperty.Name = "Parts"; var navigationProperty = carType.AddUnidirectionalNavigation(partsProperty); cars.AddNavigationTarget(navigationProperty, parts); var linkBuilder = edmModel.GetEntitySetLinkBuilder(cars); linkBuilder.AddNavigationPropertyLinkBuilder(navigationProperty, new NavigationLinkBuilder((context, property) => context.GenerateNavigationPropertyLink(property, false), true)); 

Web Api non può risolvere il tuo URL con nessuno dei modelli URI registrati.

Usa Route Debugger per capirlo. http://blogs.msdn.com/b/webdev/archive/2013/04/04/debugging-asp-net-web-api-with-route-debugger.aspx

Credo che il nostro problema sia il parametro id che passi tramite url Prova a renderlo più esplicito

 config.Routes.MapHttpRoute ( "DefaultInternalApi", "api/{controller}/{objectType}/{Id}/{relation}", defaults: new { Id = System.Web.Http.RouteParameter.Optional, } );