【问题标题】:OData: Do case insensitive comparison using ExpressionVisitorOData:使用 ExpressionVisitor 进行不区分大小写的比较
【发布时间】:2016-03-24 20:13:36
【问题描述】:

例如,我有这样的 OData 查询:

  • /Suppliers?$filter=Address/City eq 'city'
  • /Suppliers?$filter=contains(Address/City, 'city')
  • /Suppliers?$filter=endswith(Address/City, 'city')

...

并且地址/城市只有“城市”。我仍然希望查询返回该记录。

我已经阅读了this,但它似乎只是针对包含函数的地址。我可以轻松修复其他功能,但 eq 更难。 为了使它更简单,我正在考虑通过执行此 .ToString().ToUpper() 将所有字符串 const 替换为大写。

我遇到了问题,因为我无法真正访问该值。

protected override Expression VisitConstant(ConstantExpression node)
            {
//the node.Value i get here is {value(System.Web.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer`1[System.String])} 
and of type System.Linq.Expressions.ConstantExpression.

如何将值直接修改为大写或添加对 ToString 和 ToUpper 的调用?

【问题讨论】:

  • 你试过内置的tolower功能吗?例如,/Suppliers?$filter=tolower(Address/City) eq 'city'.
  • 我无法控制发送的查询,它们是由用户构建的。我可以在内部更改表达式
  • user21479 我一直在处理同样的问题,我知道如何解决它,但我不知道如何将新的表达式(ExpressionVisitor 的结果)插入查询管道。

标签: asp.net-web-api odata expressionvisitor


【解决方案1】:

理想的解决方案是使用内置的tolowertoupper 过滤函数在客户端处理此问题。这将允许客户端选择是否区分大小写进行过滤。

不幸的是,在服务器端,当前的最佳实践是修改请求 URI 并为修改后的 URI 生成一个新的请求对象。见OData V4 modify $filter on server side。 Web API OData 中有一个未解决的问题,用于更优雅的query option interception/modification mechanism

直接在请求 URI 上进行字符串手术总是容易出错。我们可以利用OData Library (ODL) 中过滤表达式的丰富对象模型来改进linked answer。请注意,ODataQueryOptions.Filter.FilterClause.Expression 上的过滤器表达式是一个抽象语法树,表示 $filter 的值。

下面的代码需要来自 ODL 的命名空间:

using Microsoft.OData.Core;
using Microsoft.OData.Core.UriBuilder;
using Microsoft.OData.Core.UriParser;
using Microsoft.OData.Core.UriParser.Semantic;
using Microsoft.OData.Core.UriParser.TreeNodeKinds;
using Microsoft.OData.Edm;

首先,定义一个帮助类来重写构成过滤器 AST 的各种节点。以下类目前仅处理 BinaryOperatorNode(例如,eq 表达式)。

public static class FilterExpressionHelper
{
    public static SingleValueNode RewriteAsCaseInsensitive(BinaryOperatorNode node)
    {
        // Handle {Edm.String eq Edm.String}
        if (node.OperatorKind == BinaryOperatorKind.Equal && node.Left.TypeReference.IsString() && node.Right.TypeReference.IsString())
        {
            // Wrap both operands with toupper().
            node = new BinaryOperatorNode(BinaryOperatorKind.Equal,
                new SingleValueFunctionCallNode("toupper", new List<QueryNode> { node.Left }, node.Left.TypeReference),
                new SingleValueFunctionCallNode("toupper", new List<QueryNode> { node.Right }, node.Right.TypeReference));
        }

        return node;
    }

    // Add methods to handle other node types; e.g., SingleValueFunctionCallNode.
}

接下来,定义一个助手来调用重写器并重新生成请求 URI 的查询字符串(由 ODataQueryOptions 建模)。

public class ODataUriHelper
{
    public static string RewriteQuery(ODataQueryOptions queryOptions)
    {
        var odataUri = BuildODataUri(queryOptions);
        var uriBuilder = new ODataUriBuilder(ODataUrlConventions.Default, odataUri);
        var uri = uriBuilder.BuildUri();
        // Do not return the leading '?'.
        return uri.Query.Substring(1);
    }

    private static readonly Uri DummyServiceRoot = new Uri("http://localhost");
    private static readonly ODataPath DummyPath = new ODataPath(Enumerable.Empty<ODataPathSegment>());

    private static ODataUri BuildODataUri(ODataQueryOptions queryOptions)
    {
        var uri = new ODataUri();

        uri.ServiceRoot = DummyServiceRoot;
        uri.Path = DummyPath;
        uri.Filter = RewriteFilter(queryOptions.Filter?.FilterClause);
        uri.OrderBy = queryOptions.OrderBy?.OrderByClause;
        uri.SelectAndExpand = queryOptions.SelectExpand?.SelectExpandClause;
        uri.Skip = queryOptions.Skip?.Value;
        uri.Top = queryOptions.Top?.Value;

        return uri;
    }

    private static FilterClause RewriteFilter(FilterClause filterClause)
    {
        if (filterClause != null)
        {
            var filterExpr = filterClause.Expression;
            var binaryExpr = filterExpr as BinaryOperatorNode;

            if (binaryExpr != null)
            {
                filterExpr = FilterExpressionHelper.RewriteAsCaseInsensitive(binaryExpr);
                filterClause = new FilterClause(filterExpr, filterClause.RangeVariable);
            }

            // Add support for other node types here.
        }

        return filterClause;
    }
}

最后,将它们与EnableQuery 属性的自定义版本绑定在一起,该属性有条件地执行重写。

public class CustomEnableQueryAttribute : EnableQueryAttribute
{
    public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions)
    {
        if (queryOptions.Filter != null)
        {
            var query = ODataUriHelper.RewriteQuery(queryOptions);
            var uri = new UriBuilder(queryOptions.Request.RequestUri) { Query = query };
            var request = new HttpRequestMessage(HttpMethod.Get, uri.Uri);

            queryOptions = new ODataQueryOptions(queryOptions.Context, request);
        }

        return base.ApplyQuery(queryable, queryOptions);
    }
}

【讨论】:

    【解决方案2】:

    简单地说:

    ?$filter=contains(tolower(siteName),tolower(%27michel%27))
                               ---^---            ---^---
                                Field           value to find
    
    ?$filter=contains(tolower(siteName),tolower(%27Michel%27))
    ?$filter=contains(tolower(siteName),tolower(%27MiCHel%27))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-08-09
      • 2011-05-28
      • 1970-01-01
      • 1970-01-01
      • 2011-01-09
      相关资源
      最近更新 更多