在OData中使用包含导航属性时获取异常

时间:2014-11-03 22:36:01

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

我在使用包含遏制的OData服务进行一些测试时遇到异常。我正在运行Web Api 2.2 / Web Api OData 5.3 / OData Lib 6.8并使用EF 6在后端使用SQL Server。以下是我的数据模型:

[Table("Product", Schema = "dbo")]
public class Product
{
    public Product()
    {
        Parts = new HashSet<Part>();
    }

    [Key]
    public int ProductID { get; set; }

    [StringLength(100)]
    public string ProductName { get; set; }

    [Contained]
    public virtual ICollection<Part> Parts { get; set; }
}

[Table("Supplier", Schema = "dbo")]
public class Supplier
{
    [Key]
    public int SupplierID { get; set; }

    [StringLength(100)]
    public string SupplierName { get; set; }
}

[Table("Part", Schema = "dbo")]
public class Part
{
    [Key]
    public int PartID { get; set; }

    public int ProductID { get; set; }

    public int SupplierID { get; set; }

    [StringLength(100)]
    public string PartName { get; set; }

    public virtual Product Product { get; set; }
    public virtual Supplier Supplier { get; set; }
}

Product实体的Parts集合标记为包含,因此无法直接查询Parts,只能通过Products实体集查询。以下URL产生预期结果:

Products?$expand=Parts:
{
  "@odata.context":"https://localhost:8732/DataApi/$metadata#Products","value":[
    {
      "ProductID":1,"ProductName":"Road runner trap","Parts@odata.context":"https://localhost:8732/DataApi/$metadata#Products(1)/Parts","Parts":[
        {
          "PartID":1,"ProductID":1,"SupplierID":1,"PartName":"Spring"
        },{
          "PartID":2,"ProductID":1,"SupplierID":1,"PartName":"Wire"
        },{
          "PartID":3,"ProductID":1,"SupplierID":1,"PartName":"Rocket"
        }
      ]
    }
  ]
}

Products(1)/Parts:
{
  "@odata.context":"https://localhost:8732/DataApi/$metadata#Products(1)/Parts","value":[
    {
      "PartID":1,"ProductID":1,"SupplierID":1,"PartName":"Spring"
    },{
      "PartID":2,"ProductID":1,"SupplierID":1,"PartName":"Wire"
    },{
      "PartID":3,"ProductID":1,"SupplierID":1,"PartName":"Rocket"
    }
  ]
}

Products(1)/Parts(3):
{
  "@odata.context":"https://localhost:8732/DataApi/$metadata#Products(1)/Parts","value":[
    {
      "PartID":3,"ProductID":1,"SupplierID":1,"PartName":"Rocket"
    }
  ]
}

Suppliers(1):
{
  "@odata.context":"https://localhost:8732/DataApi/$metadata#Suppliers","value":[
    {
      "SupplierID":1,"SupplierName":"Acme Industries"
    }
  ]
}

但是当我尝试从Parts实体获取Supplier时,我在数据服务中遇到以下异常:

产品(1)/零件(3)/供应商:

Microsoft.OData.Core.ODataException未被用户代码
处理 的HResult = -2146233079
消息=导航属性的目标实体集&#39;供应商&#39;无法找到。这很可能是IEdmModel中的一个错误 来源= Microsoft.OData.Core
堆栈跟踪:
在Microsoft.OData.Core.UriParser.Parsers.ODataPathParser.CreatePropertySegment(ODataPathSegment previous,IEdmProperty property,String queryPortion)
在Microsoft.OData.Core.UriParser.Parsers.ODataPathParser.CreateNextSegment(String text)
在Microsoft.OData.Core.UriParser.Parsers.ODataPathParser.ParsePath(ICollection`1段)
在Microsoft.OData.Core.UriParser.Parsers.ODataPathFactory.BindPath(ICollection`1段,ODataUriParserConfiguration配置)
在Microsoft.OData.Core.UriParser.ODataUriParser.ParsePathImplementation()
在Microsoft.OData.Core.UriParser.ODataUriParser.Initialize()
在System.Web.OData.Routing.DefaultODataPathHandler.Parse(IEdmModel model,String serviceRoot,String odataPath,Boolean enableUriTemplateParsing)
在System.Web.OData.Routing.DefaultODataPathHandler.Parse(IEdmModel model,String serviceRoot,String odataPath)...

如果我转到Parts实体并将Supplier navigation属性标记为Contained,则它可以正常工作:

[Table("Part", Schema = "dbo")]
public class Part
{
    [Key]
    public int PartID { get; set; }

    public int ProductID { get; set; }

    public int SupplierID { get; set; }

    [StringLength(100)]
    public string PartName { get; set; }

    public virtual Product Product { get; set; }
    [Contained]
    public virtual Supplier Supplier { get; set; }
}

Products(1)/Parts(3)/Supplier:

{
  "@odata.context":"https://localhost:8732/DataApi/$metadata#Products(1)/Parts(3)/Supplier","value":[
    {
      "SupplierID":1,"SupplierName":"Acme Industries"
    }
  ]
}

然而,这似乎不是处理此问题的正确方法。这将要求每个实体知道它是否可以通过Contained navigation属性从任何其他实体导航到,然后如果是,则将其所有导航属性标记为包含,以便可以导航整个链。

这种行为是否符合设计要求?或者还有什么我想念的东西可以解决这个问题?

修改

根据要求,以下是此过程中的其余代码:控制器,允许部件实体包含的自定义路由约定,允许我们调试异常的自定义ODataPathHandler以及所有这些都是WebApiConfig有线。

public class ProductsController : ODataController
{
    ProductsContext db = new ProductsContext();

    [EnableQuery]
    public IQueryable<Product> Get()
    {
        return db.Products;
    }
    [EnableQuery]
    public IQueryable<Product> Get(int key)
    {
        return db.Products.Where(p => p.ProductID == key);
    }

    protected override void Dispose(bool disposing)
    {
        db.Dispose();
        base.Dispose(disposing);
    }
}

public class SuppliersController : ODataController
{
    ProductsContext db = new ProductsContext();

    [EnableQuery]
    public IQueryable<Supplier> Get()
    {
        return db.Suppliers;
    }
    [EnableQuery]
    public IQueryable<Supplier> Get(int key)
    {
        return db.Suppliers.Where(s => s.SupplierID == key);
    }

    protected override void Dispose(bool disposing)
    {
        db.Dispose();
        base.Dispose(disposing);
    }
}

public class PartsController : ODataController
{
    ProductsContext db = new ProductsContext();

    [EnableQuery]
    public IQueryable<Part> GetFromRelatedEntity(int relatedEntityKey)
    {
        return db.Parts.Where(p => p.ProductID == relatedEntityKey);
    }
    [EnableQuery]
    public IQueryable<Part> GetFromRelatedEntity(int relatedEntityKey, int key)
    {
        return db.Parts.Where(p => p.PartID == key && p.ProductID == relatedEntityKey);
    }

    protected override void Dispose(bool disposing)
    {
        db.Dispose();
        base.Dispose(disposing);
    }
}

public class RelatedEntityRoutingConvention : IODataRoutingConvention
{
    public string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, ILookup<string, HttpActionDescriptor> actionMap)
    {
        if (odataPath.PathTemplate == "~/entityset/key/navigation")
        {
            string actionName = controllerContext.Request.Method.Method + "FromRelatedEntity";

            if (actionMap.Contains(actionName))
            {
                var keyValueSegment = odataPath.Segments[1] as KeyValuePathSegment;
                controllerContext.RouteData.Values["relatedEntityKey"] = keyValueSegment.Value;
                return actionName;
            }
        }
        else if (odataPath.PathTemplate == "~/entityset/key/navigation/key")
        {
            string actionName = controllerContext.Request.Method.Method + "FromRelatedEntity";

            if (actionMap.Contains(actionName))
            {
                var firstKeyValueSegment = odataPath.Segments[1] as KeyValuePathSegment;
                var secondKeyValueSegment = odataPath.Segments[3] as KeyValuePathSegment;
                controllerContext.RouteData.Values["relatedEntityKey"] = firstKeyValueSegment.Value;
                controllerContext.RouteData.Values["key"] = secondKeyValueSegment.Value;
                return actionName;
            }
        }

        return null;
    }

    public string SelectController(ODataPath odataPath, HttpRequestMessage request)
    {
        if (odataPath.PathTemplate == "~/entityset/key/navigation" || odataPath.PathTemplate == "~/entityset/key/navigation/key")
            return odataPath.NavigationSource.Name;

        return null;
    }
}

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        ODataModelBuilder builder = new ODataConventionModelBuilder();

        builder.EntitySet<Product>("Products");
        builder.EntitySet<Supplier>("Suppliers");

        List<IODataRoutingConvention> myRoutingConventions = new List<IODataRoutingConvention>();
        myRoutingConventions.Add(new RelatedEntityRoutingConvention());
        myRoutingConventions.AddRange(ODataRoutingConventions.CreateDefault());

        config.MapODataServiceRoute(
            routeName: "ODataRoute",
            routePrefix: null,
            model: builder.GetEdmModel(), 
            pathHandler: new MyODataPathHandler(), 
            routingConventions: myRoutingConventions, 
            batchHandler: new EntityFrameworkBatchHandler(new HttpServer(config))
            );
    }
}

public class MyODataPathHandler : DefaultODataPathHandler
{
    // This is where the exception occurs.
    public override ODataPath Parse(IEdmModel model, string serviceRoot, string odataPath)
    {
        return base.Parse(model, serviceRoot, odataPath);
    }
}

0 个答案:

没有答案