【问题标题】:Adding a custom query backed Navigation Property to ODataConventionModelBuilder向 ODataConventionModelBuilder 添加自定义查询支持的导航属性
【发布时间】:2014-05-29 18:15:36
【问题描述】:

情况

我创建了以下模型类

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

还有一个匹配的 DbContext

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

并使用脚手架模板“Web API 2 OData Controller with Actions, using Entity Framework”创建了一个 WebApplication 以通过 odata 提供此功能

我还创建了以下 webapi 配置:

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

我现在想将以下方法添加到我的汽车控制器

// 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;
}

并使用此 Url 检索数据:

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

但它不起作用,而是我收到以下错误:

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

问题

所以我的问题是,这可能吗?!

我尝试手动创建一个 Navigation 属性,并将其添加到 edm 模型中,虽然这确实解决了调用新方法的问题,但它也引入了新的错误。

编辑:

什么 id 尝试通过这种方式手动添加:

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

虽然这允许我通过上面指定的 URL 调用上面指定的方法,但它给了我以下错误:

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

【问题讨论】:

    标签: c# entity-framework odata asp.net-web-api asp.net-web-api2


    【解决方案1】:

    您必须在 EntitySet 上调用“AddNavigationTarget”。 假设您的命名空间是“MyNamespace”,然后将以下代码添加到您的 WebApiConfig.cs。这样,使用“Get: odata/Cars(1)/Parts”检索数据就可以了。

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

    【讨论】:

    • 是的,确实有效,如果没有办法直接对 ODataConventionModelBuilder 执行相同操作,我会将其标记为答案。谢谢
    • 我真的很想使用这个解决方案。但是,当我尝试以.../odata/Cars 查询数据时,我得到了这个异常:No NavigationLink factory was found for the navigation property 'Parts' from entity type 'OdataExperiments.Models.Car' on entity set 'Cars'. Try calling HasNavigationPropertyLink on the EntitySetConfiguration.&amp;#xD; Parameter name: navigationProperty 知道如何解决这个问题吗?
    • @veertien:这是因为您正在尝试访问导航属性,但我们无法在这两个 CLR 类型之间没有导航的实体集之间添加导航。你可以试试 untype 场景:aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/OData/…
    • @LunicLynx 我已经研究了如何直接在此答案中对 ODataConventionModelBuilder 执行相同操作:stackoverflow.com/questions/27568899/…
    • 你能用这个方法给导航属性应用模型别名吗?
    【解决方案2】:

    进一步了解@FengZhao 的答案,为了使 url odata/Cars 正常工作,您还需要将导航属性链接构建器注册到实体集链接构建器。

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

    【讨论】:

      【解决方案3】:

      Web Api 无法针对任何已注册的 URI 模板解析您的 URL。

      使用路由调试器来解决这个问题。 http://blogs.msdn.com/b/webdev/archive/2013/04/04/debugging-asp-net-web-api-with-route-debugger.aspx

      我相信我们的问题是您通过 url 传递的 id 参数 试着让它更明确

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

      【讨论】:

      • 我不应该通过ODataConventionModelBuilder添加一个导航属性,以便它知道如何解决它吗?我确实让它调用了我的方法,但它给了我另一个错误,告诉我它不知道实体集或其他东西。
      【解决方案4】:

      如果对他们有意义的话,最初的问题也可以通过将 Parts 添加为 Car 对象的属性来解决他们的问题。也就是说,添加真正的导航属性,而不是说服 OData 模型构建器注册它。例如:

      public class Car
      {
          public int Id {get;set;}
          public string Name {get;set;}
      
          public virtual ICollection<PartState> PartStates {get;set; }
          public virtual ICollection<Part> Parts  { get => this.PartStates?.Select(partState => partState.Part)
      }
      

      【讨论】:

        猜你喜欢
        • 2013-04-15
        • 2013-03-12
        • 2011-10-15
        • 1970-01-01
        • 2014-09-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多