我在使用包含遏制的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);
}
}