如何将 table 值函数公开为 Web Api 2 OData v4 服务中实体集的 属性?
How do I expose a table valued function as a property of an entity set within a Web Api 2 OData v4 service?
我需要帮助弄清楚如何将 table 值函数公开为 Web Api 2 OData v4 服务中实体集的 属性。
我的简化模式具有三个 table,结构、位置和位置链接。结构包含带有节点 (Locatons) 和边 (LocationLinks) 的图。我使用 Entity Framework 6 数据库优先模型进行访问。
Simplified Schema
Structure:
ID
Locations:
ID
ParentID -> Structure
LocationLinks
A -> Location
B -> Location
目标是像访问结构的位置一样访问 LocationLinks 的结构集合。即请求结构#180 的图表:
http://.../OData/Structures(180)/LocationLinks
http://.../OData/Structures(180)/Locations
位置查询会自动运行,但我不知道如何添加正确的路线来启用 LocationLinks 查询。我认为这会让我的任务更容易,所以我在我的 SQL 服务器上添加了一个 Table Value Function。该函数存在于我的 EF 模型和 returns LocationLink 实体的集合中:
StructureLocationLinks(@StructureID) -> LocationLinks
不幸的是,无论我尝试什么,我似乎都无法使 Structure(180)\LocationLinks URL 发挥作用。这是我最近的尝试:
StructuresController.cs 片段:
// GET: odata/Structures(5)/Locations
[EnableQuery]
public IQueryable<Location> GetLocations([FromODataUri] long key)
{
return db.Structures.Where(m => m.ID == key).SelectMany(m => m.Locations);
}
// GET: odata/Structures(5)/LocationLinks
[EnableQuery]
//[System.Web.OData.Routing.ODataRoute("Structures({key})")]
public IQueryable<LocationLink> GetLocationLinks([FromODataUri] long key)
{
return db.StructureLocationLinks(key);
}
WebApi.cs 片段:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.UseDataContractJsonSerializer = true;
//json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.All;
//var cors = new System.Web.Http.Cors.EnableCorsAttribute("*", "*", "*");
//config.EnableCors(cors);
// Web API routes
config.MapHttpAttributeRoutes();
ODataConventionModelBuilder builder = GetModel();
config.MapODataServiceRoute(routeName: "odata",
routePrefix: null,
model: builder.GetEdmModel());
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
public static ODataConventionModelBuilder GetModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.Namespace = typeof(Structure).Namespace;
AddLocationLinks(builder);
AddStructures(builder);
builder.EntitySet<Location>("Locations");
return builder;
}
public static void AddStructures(ODataModelBuilder builder)
{
var structSetconfig = builder.EntitySet<Structure>("Structures");
var structConfig = structSetconfig.EntityType;
var functionConfig = structConfig.Collection.Function("StructureLocationLinks");
functionConfig.Parameter<long>("StructureID");
functionConfig.Returns<LocationLink>();
}
public static void AddLocationLinks(ODataModelBuilder builder)
{
var type = builder.EntityType<LocationLink>();
type.HasKey(sl => sl.A);
type.HasKey(sl => sl.B);
builder.EntitySet<LocationLink>("LocationLinks");
}
我收到的错误是:
{
"error":{
"code":"","message":"No HTTP resource was found that matches the request URI
'http://.../OData/Structures(180)/LocationLinks'.","innererror":{
"message":"No routing convention was found to select an action for the OData path with template
'~/entityset/key/unresolved'.","type":"","stacktrace":""
} } }
根据一些搜索,我尝试将 ODataRoute 属性添加到控制器:
// GET: odata/Structures(5)/LocationLinks
[EnableQuery]
[System.Web.OData.Routing.ODataRoute("Structures({key})/LocationLinks")]
public IQueryable<LocationLink> GetLocationLinks([FromODataUri] long key)
{
return db.StructureLocationLinks(key);
}
导致此错误的结果:
The path template 'Structures({key})/LocationLinks' on the action
'GetLocationLinks' in controller 'Structures' is not a valid OData
path template. Found an unresolved path segment 'LocationLinks' in the
OData path template 'Structures({key})/LocationLinks'.
如何从 Structures 集合中公开 LocationLinks?谢谢你的时间。
编辑:
在发现这个问题后,我成功地解决了这个问题:
Adding a custom query backed Navigation Property to ODataConventionModelBuilder
我必须在我的 ODataConventionBuilder 对象上调用 .GetEdmModel,然后使用此函数将导航 属性 添加到模型中:
private static Microsoft.OData.Edm.IEdmModel AddStructureLocationLinks(IEdmModel edmModel)
{
var structures = edmModel.EntityContainer.FindEntitySet("Structures") as EdmEntitySet;
var locationLinks = edmModel.EntityContainer.FindEntitySet("LocationLinks") as EdmEntitySet;
var structType = structures.EntityType() as EdmEntityType;
var locLinksType = locationLinks.EntityType() as EdmEntityType;
var structLocLinksProperty = new EdmNavigationPropertyInfo();
structLocLinksProperty.TargetMultiplicity = Microsoft.OData.Edm.EdmMultiplicity.Many;
structLocLinksProperty.Target = locLinksType;
structLocLinksProperty.ContainsTarget = true;
structLocLinksProperty.OnDelete = Microsoft.OData.Edm.EdmOnDeleteAction.None;
structLocLinksProperty.Name = "LocationLinks";
var navigationProperty = structType.AddUnidirectionalNavigation(structLocLinksProperty);
structures.AddNavigationTarget(navigationProperty, locationLinks);
return edmModel;
}
我现在遇到的问题是我在查询中访问导航 属性 的能力有限。例如这个 link 有效:
http://.../OData/Structures(180)/Children?$expand=Locations
虽然这不是。
http://.../OData/Structures(180)/Children?$expand=LocationLinks
返回的错误是
{ "error": {
"code":"","message":"An error has occurred.","innererror":
{
"message":"Instance property 'LocationLinks' is not defined for type 'ConnectomeDataModel.Structure'",
"type":"System.ArgumentException","stacktrace":" at System.Linq.Expressions.Expression.Property(Expression expression,
String propertyName)\r\n at
System.Web.OData.Query.Expressions.SelectExpandBinder.CreatePropertyValueExpressionWithFilter(IEdmEntityType
elementType, IEdmProperty property, Expression source, FilterClause
filterClause)\r\n at
System.Web.OData.Query.Expressions.SelectExpandBinder.BuildPropertyContainer(IEdmEntityType
elementType, Expression source, Dictionary2 propertiesToExpand,
ISet
1 propertiesToInclude, ISet1 autoSelectedProperties, Boolean
isSelectingOpenTypeSegments)\r\n at
System.Web.OData.Query.Expressions.SelectExpandBinder.ProjectElement(Expression
source, SelectExpandClause selectExpandClause, IEdmEntityType
entityType)\r\n at
System.Web.OData.Query.Expressions.SelectExpandBinder.Bind(IQueryable
queryable)\r\n at
System.Web.OData.Query.ODataQueryOptions.ApplySelectExpand[T](T
entity, ODataQuerySettings querySettings)\r\n at
System.Web.OData.Query.ODataQueryOptions.ApplyTo(IQueryable query,
ODataQuerySettings querySettings)\r\n at
System.Web.OData.EnableQueryAttribute.ExecuteQuery(Object response,
HttpRequestMessage request, HttpActionDescriptor actionDescriptor)\r\n
at
System.Web.OData.EnableQueryAttribute.OnActionExecuted(HttpActionExecutedContext
actionExecutedContext)\r\n at
System.Web.Http.Filters.ActionFilterAttribute.OnActionExecutedAsync(HttpActionExecutedContext
actionExecutedContext, 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.Web.Http.Filters.ActionFilterAttribute.<CallOnActionExecutedAsync>d__5.MoveNext()\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.Web.Http.Filters.ActionFilterAttribute.<ExecuteActionFilterAsyncCore>d__0.MoveNext()\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
1.GetResult()\r\n at
System.Web.Http.Controllers.ActionFilterResult.d__2.MoveNext()\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`1.GetResult()\r\n at
System.Web.Http.Dispatcher.HttpControllerDispatcher.d__1.MoveNext()"
} } }
Xycor
正如您所说,LocationLinks
应该与 Structure
的 Locations
相同。所以,我认为你为 controller 和 action 所做的是正确的。我根据您的描述进行了测试,Web API OData 似乎可以按照惯例路由 GetLocationLinks
。
让我展示一下我的元数据文档,请忽略命名空间并将其与您的进行比较,让我知道有什么不同。谢谢。
<Schema Namespace="ODataConsoleSample" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityType Name="LocationLink">
<Key>
<PropertyRef Name="A" />
<PropertyRef Name="B" />
</Key>
<Property Name="A" Type="Edm.Int64" Nullable="false" />
<Property Name="B" Type="Edm.Int64" Nullable="false" />
</EntityType>
<EntityType Name="Structure">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Edm.Int64" Nullable="false" />
<NavigationProperty Name="Locations" Type="Collection(ODataConsoleSample.Location)" />
<NavigationProperty Name="LocationLinks" Type="Collection(ODataConsoleSample.LocationLink)" />
</EntityType>
<EntityType Name="Location">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Edm.Int64" Nullable="false" />
<Property Name="ParentId" Type="Edm.Int64" Nullable="false" />
</EntityType>
<Function Name="StructureLocationLinks" IsBound="true">
<Parameter Name="bindingParameter" Type="Collection(ODataConsoleSample.Structure)" />
<Parameter Name="StructureID" Type="Edm.Int64" Nullable="false" />
<ReturnType Type="ODataConsoleSample.LocationLink" />
</Function>
<EntityContainer Name="Container">
<EntitySet Name="LocationLinks" EntityType="ODataConsoleSample.LocationLink" />
<EntitySet Name="Structures" EntityType="ODataConsoleSample.Structure">
<NavigationPropertyBinding Path="Locations" Target="Locations" />
<NavigationPropertyBinding Path="LocationLinks" Target="LocationLinks" />
</EntitySet>
<EntitySet Name="Locations" EntityType="ODataConsoleSample.Location" />
</EntityContainer>
</Schema>
我需要帮助弄清楚如何将 table 值函数公开为 Web Api 2 OData v4 服务中实体集的 属性。
我的简化模式具有三个 table,结构、位置和位置链接。结构包含带有节点 (Locatons) 和边 (LocationLinks) 的图。我使用 Entity Framework 6 数据库优先模型进行访问。
Simplified Schema
Structure:
ID
Locations:
ID
ParentID -> Structure
LocationLinks
A -> Location
B -> Location
目标是像访问结构的位置一样访问 LocationLinks 的结构集合。即请求结构#180 的图表:
http://.../OData/Structures(180)/LocationLinks
http://.../OData/Structures(180)/Locations
位置查询会自动运行,但我不知道如何添加正确的路线来启用 LocationLinks 查询。我认为这会让我的任务更容易,所以我在我的 SQL 服务器上添加了一个 Table Value Function。该函数存在于我的 EF 模型和 returns LocationLink 实体的集合中:
StructureLocationLinks(@StructureID) -> LocationLinks
不幸的是,无论我尝试什么,我似乎都无法使 Structure(180)\LocationLinks URL 发挥作用。这是我最近的尝试:
StructuresController.cs 片段:
// GET: odata/Structures(5)/Locations
[EnableQuery]
public IQueryable<Location> GetLocations([FromODataUri] long key)
{
return db.Structures.Where(m => m.ID == key).SelectMany(m => m.Locations);
}
// GET: odata/Structures(5)/LocationLinks
[EnableQuery]
//[System.Web.OData.Routing.ODataRoute("Structures({key})")]
public IQueryable<LocationLink> GetLocationLinks([FromODataUri] long key)
{
return db.StructureLocationLinks(key);
}
WebApi.cs 片段:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.UseDataContractJsonSerializer = true;
//json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.All;
//var cors = new System.Web.Http.Cors.EnableCorsAttribute("*", "*", "*");
//config.EnableCors(cors);
// Web API routes
config.MapHttpAttributeRoutes();
ODataConventionModelBuilder builder = GetModel();
config.MapODataServiceRoute(routeName: "odata",
routePrefix: null,
model: builder.GetEdmModel());
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
public static ODataConventionModelBuilder GetModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.Namespace = typeof(Structure).Namespace;
AddLocationLinks(builder);
AddStructures(builder);
builder.EntitySet<Location>("Locations");
return builder;
}
public static void AddStructures(ODataModelBuilder builder)
{
var structSetconfig = builder.EntitySet<Structure>("Structures");
var structConfig = structSetconfig.EntityType;
var functionConfig = structConfig.Collection.Function("StructureLocationLinks");
functionConfig.Parameter<long>("StructureID");
functionConfig.Returns<LocationLink>();
}
public static void AddLocationLinks(ODataModelBuilder builder)
{
var type = builder.EntityType<LocationLink>();
type.HasKey(sl => sl.A);
type.HasKey(sl => sl.B);
builder.EntitySet<LocationLink>("LocationLinks");
}
我收到的错误是:
{ "error":{ "code":"","message":"No HTTP resource was found that matches the request URI 'http://.../OData/Structures(180)/LocationLinks'.","innererror":{ "message":"No routing convention was found to select an action for the OData path with template '~/entityset/key/unresolved'.","type":"","stacktrace":"" } } }
根据一些搜索,我尝试将 ODataRoute 属性添加到控制器:
// GET: odata/Structures(5)/LocationLinks
[EnableQuery]
[System.Web.OData.Routing.ODataRoute("Structures({key})/LocationLinks")]
public IQueryable<LocationLink> GetLocationLinks([FromODataUri] long key)
{
return db.StructureLocationLinks(key);
}
导致此错误的结果:
The path template 'Structures({key})/LocationLinks' on the action 'GetLocationLinks' in controller 'Structures' is not a valid OData path template. Found an unresolved path segment 'LocationLinks' in the OData path template 'Structures({key})/LocationLinks'.
如何从 Structures 集合中公开 LocationLinks?谢谢你的时间。
编辑:
在发现这个问题后,我成功地解决了这个问题: Adding a custom query backed Navigation Property to ODataConventionModelBuilder
我必须在我的 ODataConventionBuilder 对象上调用 .GetEdmModel,然后使用此函数将导航 属性 添加到模型中:
private static Microsoft.OData.Edm.IEdmModel AddStructureLocationLinks(IEdmModel edmModel)
{
var structures = edmModel.EntityContainer.FindEntitySet("Structures") as EdmEntitySet;
var locationLinks = edmModel.EntityContainer.FindEntitySet("LocationLinks") as EdmEntitySet;
var structType = structures.EntityType() as EdmEntityType;
var locLinksType = locationLinks.EntityType() as EdmEntityType;
var structLocLinksProperty = new EdmNavigationPropertyInfo();
structLocLinksProperty.TargetMultiplicity = Microsoft.OData.Edm.EdmMultiplicity.Many;
structLocLinksProperty.Target = locLinksType;
structLocLinksProperty.ContainsTarget = true;
structLocLinksProperty.OnDelete = Microsoft.OData.Edm.EdmOnDeleteAction.None;
structLocLinksProperty.Name = "LocationLinks";
var navigationProperty = structType.AddUnidirectionalNavigation(structLocLinksProperty);
structures.AddNavigationTarget(navigationProperty, locationLinks);
return edmModel;
}
我现在遇到的问题是我在查询中访问导航 属性 的能力有限。例如这个 link 有效:
http://.../OData/Structures(180)/Children?$expand=Locations
虽然这不是。
http://.../OData/Structures(180)/Children?$expand=LocationLinks
返回的错误是
{ "error": { "code":"","message":"An error has occurred.","innererror": { "message":"Instance property 'LocationLinks' is not defined for type 'ConnectomeDataModel.Structure'", "type":"System.ArgumentException","stacktrace":" at System.Linq.Expressions.Expression.Property(Expression expression, String propertyName)\r\n at System.Web.OData.Query.Expressions.SelectExpandBinder.CreatePropertyValueExpressionWithFilter(IEdmEntityType elementType, IEdmProperty property, Expression source, FilterClause filterClause)\r\n at System.Web.OData.Query.Expressions.SelectExpandBinder.BuildPropertyContainer(IEdmEntityType elementType, Expression source, Dictionary
2 propertiesToExpand, ISet
1 propertiesToInclude, ISet1 autoSelectedProperties, Boolean isSelectingOpenTypeSegments)\r\n at System.Web.OData.Query.Expressions.SelectExpandBinder.ProjectElement(Expression source, SelectExpandClause selectExpandClause, IEdmEntityType entityType)\r\n at System.Web.OData.Query.Expressions.SelectExpandBinder.Bind(IQueryable queryable)\r\n at System.Web.OData.Query.ODataQueryOptions.ApplySelectExpand[T](T entity, ODataQuerySettings querySettings)\r\n at System.Web.OData.Query.ODataQueryOptions.ApplyTo(IQueryable query, ODataQuerySettings querySettings)\r\n at System.Web.OData.EnableQueryAttribute.ExecuteQuery(Object response, HttpRequestMessage request, HttpActionDescriptor actionDescriptor)\r\n at System.Web.OData.EnableQueryAttribute.OnActionExecuted(HttpActionExecutedContext actionExecutedContext)\r\n at System.Web.Http.Filters.ActionFilterAttribute.OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, 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.Web.Http.Filters.ActionFilterAttribute.<CallOnActionExecutedAsync>d__5.MoveNext()\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.Web.Http.Filters.ActionFilterAttribute.<ExecuteActionFilterAsyncCore>d__0.MoveNext()\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
1.GetResult()\r\n at System.Web.Http.Controllers.ActionFilterResult.d__2.MoveNext()\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`1.GetResult()\r\n at System.Web.Http.Dispatcher.HttpControllerDispatcher.d__1.MoveNext()" } } }
Xycor
正如您所说,LocationLinks
应该与 Structure
的 Locations
相同。所以,我认为你为 controller 和 action 所做的是正确的。我根据您的描述进行了测试,Web API OData 似乎可以按照惯例路由 GetLocationLinks
。
让我展示一下我的元数据文档,请忽略命名空间并将其与您的进行比较,让我知道有什么不同。谢谢。
<Schema Namespace="ODataConsoleSample" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityType Name="LocationLink">
<Key>
<PropertyRef Name="A" />
<PropertyRef Name="B" />
</Key>
<Property Name="A" Type="Edm.Int64" Nullable="false" />
<Property Name="B" Type="Edm.Int64" Nullable="false" />
</EntityType>
<EntityType Name="Structure">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Edm.Int64" Nullable="false" />
<NavigationProperty Name="Locations" Type="Collection(ODataConsoleSample.Location)" />
<NavigationProperty Name="LocationLinks" Type="Collection(ODataConsoleSample.LocationLink)" />
</EntityType>
<EntityType Name="Location">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Edm.Int64" Nullable="false" />
<Property Name="ParentId" Type="Edm.Int64" Nullable="false" />
</EntityType>
<Function Name="StructureLocationLinks" IsBound="true">
<Parameter Name="bindingParameter" Type="Collection(ODataConsoleSample.Structure)" />
<Parameter Name="StructureID" Type="Edm.Int64" Nullable="false" />
<ReturnType Type="ODataConsoleSample.LocationLink" />
</Function>
<EntityContainer Name="Container">
<EntitySet Name="LocationLinks" EntityType="ODataConsoleSample.LocationLink" />
<EntitySet Name="Structures" EntityType="ODataConsoleSample.Structure">
<NavigationPropertyBinding Path="Locations" Target="Locations" />
<NavigationPropertyBinding Path="LocationLinks" Target="LocationLinks" />
</EntitySet>
<EntitySet Name="Locations" EntityType="ODataConsoleSample.Location" />
</EntityContainer>
</Schema>