将 Distinct 应用于 OData 查询
Applying Distinct to OData query
我想从我的 OData 端点获取不同值的列表。但尚不支持 distinct 或 group by。
我的 URI 查询看起来像这样
GET /odata/Products?$select=foo & $top=10 & $count=true & distinct=true
我的控制器
[EnableQuery]
public IQueryable<FooBarBaz> Get(ODataQueryOptions<FooBarBaz> queryOptions, bool distinct)
{
//I've tried the following
return Repository.AsQueryable().Distinct();
// and
return Repository.AsQueryable().GroupBy(x => x.Foo);
// and
IQueryable query = queryOptions.ApplyTo(Repository.AsQueryable());
return query.Distinct(); // Can't call .Distinct() here
}
None 工作:(
解决问题的最佳方案是在资源上定义一个集合Action。
第一步:在WebApiConfig.cs中配置'Distinct'操作
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<FooBarBaz>("FooBarBazs");//Resource Name
ActionConfiguration Distinct = builder.Entity<FooBarBaz>().Collection.Action("Distinct");//Name of the action method
Distinct.ReturnsCollectionFromEntitySet<FooBarBaz>("FooBarBazs");//Return type of action
Distinct.Parameter<string>("On");//Property on which collection is filtered as Distinct
config.Routes.MapODataRoute("odata", "odata", builder.GetEdmModel());
第二步:在FooBarBazsController.cs中添加动作returns不同实体的集合
[EnableQuery]//enable the $select,$expend Queries
[HttpPost]//All the action methods are of post type in Web api
public IQueryable<FooBarBaz> Distinct(ODataActionParameters parameters)
{
string on = "";
if (!ModelState.IsValid)
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
try
{
on = parameters["On"] as string;
}
catch (NullReferenceException ex)
{
HttpResponseMessage message = new HttpResponseMessage(HttpStatusCode.BadRequest);
message.Content = new StringContent("{\"Error\":\"Invalid Query -> On property is not defined\"}");
throw new HttpResponseException(message);
}
catch (Exception ex)
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
PropertyInfo[] props = new FooBarBaz().GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
var isPropertyExist = false;
for (int i = 0; i < props.Length; i++)
{
if (props[i].Name.Equals(on))
{
isPropertyExist = true;
break;
}
}
if (isPropertyExist)
{
var fooBarBazCollection = db.fooBarBazs.GroupBy(GetGroupKey(on)).Select(g => g.FirstOrDefault());//Select the Distinct Entity on the basis of a property
return fooBarBazCollection ;
}
else
{
HttpResponseMessage message = new HttpResponseMessage(HttpStatusCode.BadRequest);
message.Content = new StringContent("{\"Error\":\"Property '"+on+"' Not Exist}");
throw new HttpResponseException(message);
}
}
第三步: 添加一个静态方法,该方法returns 基于属性 Name.
groupby 的表达式。
private static Expression<Func<fooBarBaz, string>> GetGroupKey(string property)
{
var parameter = Expression.Parameter(typeof(fooBarBaz));
var body = Expression.Property(parameter, property);
return Expression.Lambda<Func<fooBarBaz, string>>(body, parameter);
}
现在构建项目,您可以像这样查询资源
POST /odata/FooBarBazs/Distinct HTTP/1.1
Host: localhost:9360
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 6d174086-7b97-76a2-679c-4dab3dfb5938
{"On":"PropertyName"}
也可以像这样使用 $select 和 $expend
POST /odata/FooBarBazs/Distinct?$select=PropertyName1,PropertyName2 HTTP/1.1
Host: localhost:9360
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 6d174086-7b97-76a2-679c-4dab3dfb5938
{"On":"PropertyName"}
我希望这能解决问题。 +1 如果有。
因为您指定了 EnableQuery 属性,所以您可以使用 $apply 对不同的字段进行分组,而无需添加任何自定义函数或参数,您可以免费获得:
GET /odata/Products?$apply=groupby((foo))&top=10&$count=true
这是简单的 OData v4 标准语法,不需要修改任何代码即可实现。不要去改变你想要支持不同查询的每个控制器,你不能提前 100% 知道你的客户端应用程序可能需要这个功能的控制器,所以在你开始定制之前使用提供的功能。
当然,这种方法有一个警告,它不能使它在 100% 的时间内可行:
- $filter 和$orderby 只能对你的group by 子句指定的字段进行操作
这可能需要您在分组语句中包含额外的字段,并且对于某些复杂的过滤,结果数据集可能不令人满意,在这种情况下,我们发现支持传递额外的 pre-filter通过 HTTP header 的参数可以在应用传入的查询选项之前应用于查询,请注意,这只是必要的,因为我们的过滤条件与租赁和安全相关,因此结果数据集有更多重复条目如果您忽略了安全描述符。
只是为了好玩,这是我们的自定义 GET 函数,如果传入 pre-filter:
[EnableQuery]
public IQueryable<FooBarBaz> Get(ODataQueryOptions<FooBarBaz> queryOptions, bool distinct)
{
DbQuery<FooBarBaz> query = Repository;
query = this.ApplyUserPolicy(query);
return Ok(query);
}
以下是在基础 class 中实现的,因此我们不会在每个控制器中都有它:
/// <summary>
/// Apply default user policy to the DBQuery that will be used by actions on this controller.
/// The big one we support here is X-Filter HTTP headers, so now you can provide top level filtering in the header of the request
/// before the normal OData filter and query parameters are applied.
/// This is useful when you want to use $apply and $filter together but on separate sets of conditions.
/// </summary>
/// <param name="dataTable">DBQuery to apply the policy to</param>
/// <returns>Returns IQueryable entity query ready for processing with the headers applied (if any)</returns>
private IQueryable<TEntity> ApplyUserPolicy(DbQuery<TEntity> dataTable)
{
// Proprietary Implementation of Security Tokens
//var tokenData = SystemController.CurrentToken(Request);
//IQueryable<TEntity> query = ApplyUserPolicy(dataTable, tokenData);
IQueryable<TEntity> query = dataTable.AsQueryable();
// Now try and apply an OData filter passed in as a header.
// This means we are applying a global filter BEFORE the normal OData query params
// ... we can filter before $apply and group by
System.Collections.Generic.IEnumerable<string> filters = null;
if (Request.Headers.TryGetValues("X-Filter", out filters))
{
foreach (var filter in filters)
{
//var expressions = filter.Split(',');
//foreach (var expression in expressions)
{
var expression = filter;
Dictionary<string, string> options = new Dictionary<string, string>()
{
{ "$filter" , expression },
};
var model = this.Request.ODataProperties().Model;
IEdmNavigationSource source = model.FindDeclaredEntitySet(this.GetEntitySetName());
var type = source.EntityType();
Microsoft.OData.Core.UriParser.ODataQueryOptionParser parser
= new Microsoft.OData.Core.UriParser.ODataQueryOptionParser(model, type, source, options);
var filterClause = parser.ParseFilter(); // parse $filter
FilterQueryOption option = new FilterQueryOption(expression, new ODataQueryContext(model, typeof(TEntity), this.Request.ODataProperties().Path), parser);
query = (IQueryable<TEntity>)option.ApplyTo(query, new ODataQuerySettings());
}
}
}
return query;
}
如果不出意外,尝试将 AdaptiveLINQ 卖给您的经理会更便宜:)
我想从我的 OData 端点获取不同值的列表。但尚不支持 distinct 或 group by。
我的 URI 查询看起来像这样
GET /odata/Products?$select=foo & $top=10 & $count=true & distinct=true
我的控制器
[EnableQuery]
public IQueryable<FooBarBaz> Get(ODataQueryOptions<FooBarBaz> queryOptions, bool distinct)
{
//I've tried the following
return Repository.AsQueryable().Distinct();
// and
return Repository.AsQueryable().GroupBy(x => x.Foo);
// and
IQueryable query = queryOptions.ApplyTo(Repository.AsQueryable());
return query.Distinct(); // Can't call .Distinct() here
}
None 工作:(
解决问题的最佳方案是在资源上定义一个集合Action。
第一步:在WebApiConfig.cs中配置'Distinct'操作
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<FooBarBaz>("FooBarBazs");//Resource Name
ActionConfiguration Distinct = builder.Entity<FooBarBaz>().Collection.Action("Distinct");//Name of the action method
Distinct.ReturnsCollectionFromEntitySet<FooBarBaz>("FooBarBazs");//Return type of action
Distinct.Parameter<string>("On");//Property on which collection is filtered as Distinct
config.Routes.MapODataRoute("odata", "odata", builder.GetEdmModel());
第二步:在FooBarBazsController.cs中添加动作returns不同实体的集合
[EnableQuery]//enable the $select,$expend Queries
[HttpPost]//All the action methods are of post type in Web api
public IQueryable<FooBarBaz> Distinct(ODataActionParameters parameters)
{
string on = "";
if (!ModelState.IsValid)
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
try
{
on = parameters["On"] as string;
}
catch (NullReferenceException ex)
{
HttpResponseMessage message = new HttpResponseMessage(HttpStatusCode.BadRequest);
message.Content = new StringContent("{\"Error\":\"Invalid Query -> On property is not defined\"}");
throw new HttpResponseException(message);
}
catch (Exception ex)
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
PropertyInfo[] props = new FooBarBaz().GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
var isPropertyExist = false;
for (int i = 0; i < props.Length; i++)
{
if (props[i].Name.Equals(on))
{
isPropertyExist = true;
break;
}
}
if (isPropertyExist)
{
var fooBarBazCollection = db.fooBarBazs.GroupBy(GetGroupKey(on)).Select(g => g.FirstOrDefault());//Select the Distinct Entity on the basis of a property
return fooBarBazCollection ;
}
else
{
HttpResponseMessage message = new HttpResponseMessage(HttpStatusCode.BadRequest);
message.Content = new StringContent("{\"Error\":\"Property '"+on+"' Not Exist}");
throw new HttpResponseException(message);
}
}
第三步: 添加一个静态方法,该方法returns 基于属性 Name.
groupby 的表达式。private static Expression<Func<fooBarBaz, string>> GetGroupKey(string property)
{
var parameter = Expression.Parameter(typeof(fooBarBaz));
var body = Expression.Property(parameter, property);
return Expression.Lambda<Func<fooBarBaz, string>>(body, parameter);
}
现在构建项目,您可以像这样查询资源
POST /odata/FooBarBazs/Distinct HTTP/1.1
Host: localhost:9360
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 6d174086-7b97-76a2-679c-4dab3dfb5938
{"On":"PropertyName"}
也可以像这样使用 $select 和 $expend
POST /odata/FooBarBazs/Distinct?$select=PropertyName1,PropertyName2 HTTP/1.1
Host: localhost:9360
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 6d174086-7b97-76a2-679c-4dab3dfb5938
{"On":"PropertyName"}
我希望这能解决问题。 +1 如果有。
因为您指定了 EnableQuery 属性,所以您可以使用 $apply 对不同的字段进行分组,而无需添加任何自定义函数或参数,您可以免费获得:
GET /odata/Products?$apply=groupby((foo))&top=10&$count=true
这是简单的 OData v4 标准语法,不需要修改任何代码即可实现。不要去改变你想要支持不同查询的每个控制器,你不能提前 100% 知道你的客户端应用程序可能需要这个功能的控制器,所以在你开始定制之前使用提供的功能。
当然,这种方法有一个警告,它不能使它在 100% 的时间内可行:
- $filter 和$orderby 只能对你的group by 子句指定的字段进行操作
这可能需要您在分组语句中包含额外的字段,并且对于某些复杂的过滤,结果数据集可能不令人满意,在这种情况下,我们发现支持传递额外的 pre-filter通过 HTTP header 的参数可以在应用传入的查询选项之前应用于查询,请注意,这只是必要的,因为我们的过滤条件与租赁和安全相关,因此结果数据集有更多重复条目如果您忽略了安全描述符。
只是为了好玩,这是我们的自定义 GET 函数,如果传入 pre-filter:
[EnableQuery]
public IQueryable<FooBarBaz> Get(ODataQueryOptions<FooBarBaz> queryOptions, bool distinct)
{
DbQuery<FooBarBaz> query = Repository;
query = this.ApplyUserPolicy(query);
return Ok(query);
}
以下是在基础 class 中实现的,因此我们不会在每个控制器中都有它:
/// <summary>
/// Apply default user policy to the DBQuery that will be used by actions on this controller.
/// The big one we support here is X-Filter HTTP headers, so now you can provide top level filtering in the header of the request
/// before the normal OData filter and query parameters are applied.
/// This is useful when you want to use $apply and $filter together but on separate sets of conditions.
/// </summary>
/// <param name="dataTable">DBQuery to apply the policy to</param>
/// <returns>Returns IQueryable entity query ready for processing with the headers applied (if any)</returns>
private IQueryable<TEntity> ApplyUserPolicy(DbQuery<TEntity> dataTable)
{
// Proprietary Implementation of Security Tokens
//var tokenData = SystemController.CurrentToken(Request);
//IQueryable<TEntity> query = ApplyUserPolicy(dataTable, tokenData);
IQueryable<TEntity> query = dataTable.AsQueryable();
// Now try and apply an OData filter passed in as a header.
// This means we are applying a global filter BEFORE the normal OData query params
// ... we can filter before $apply and group by
System.Collections.Generic.IEnumerable<string> filters = null;
if (Request.Headers.TryGetValues("X-Filter", out filters))
{
foreach (var filter in filters)
{
//var expressions = filter.Split(',');
//foreach (var expression in expressions)
{
var expression = filter;
Dictionary<string, string> options = new Dictionary<string, string>()
{
{ "$filter" , expression },
};
var model = this.Request.ODataProperties().Model;
IEdmNavigationSource source = model.FindDeclaredEntitySet(this.GetEntitySetName());
var type = source.EntityType();
Microsoft.OData.Core.UriParser.ODataQueryOptionParser parser
= new Microsoft.OData.Core.UriParser.ODataQueryOptionParser(model, type, source, options);
var filterClause = parser.ParseFilter(); // parse $filter
FilterQueryOption option = new FilterQueryOption(expression, new ODataQueryContext(model, typeof(TEntity), this.Request.ODataProperties().Path), parser);
query = (IQueryable<TEntity>)option.ApplyTo(query, new ODataQuerySettings());
}
}
}
return query;
}
如果不出意外,尝试将 AdaptiveLINQ 卖给您的经理会更便宜:)