OData: проводить сравнение без учета регистра, используя ExpressionVisitor

Например, у меня есть запрос OData, такой как они:

  • / Поставщики?$ Filter= Address / City eq 'city'
  • / Поставщики?$ Filter= содержит (Адрес / Город, 'Город')
  • / Поставщики?$ Фильтр = заканчивается с (Адрес / Город, 'город')

...

и Адрес / Город имеет только "Город". Я все еще хочу, чтобы запрос вернул эту запись.

Я уже прочитал это, но это только кажется, что адрес содержит функцию. Я мог бы легко исправить для других функций, но eq сложнее. Чтобы сделать это проще, я думал о том, чтобы просто заменить всю строку const на прописные, выполнив это.ToString().ToUpper().

У меня проблема, потому что я не могу получить доступ к значению.

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?

2 ответа

Идеальным решением было бы справиться с этим на стороне клиента с помощью встроенного tolower а также toupper функции фильтра. Это позволило бы клиенту сделать выбор: фильтровать с учетом регистра или без него.

На стороне сервера, в настоящее время, к сожалению, рекомендуется изменить URI запроса и сгенерировать новый объект запроса для измененного URI. Смотрите OData V4, изменить $filter на стороне сервера. Существует открытая проблема в OData Web API для более элегантного механизма перехвата / изменения параметров запроса.

Операция со строками непосредственно по URI запроса всегда будет подвержена ошибкам. Мы можем улучшить связанный ответ, воспользовавшись расширенной объектной моделью для выражений фильтра в библиотеке OData (ODL). Обратите внимание, что выражение фильтра на 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);
    }
}

Просто:

?$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))
Другие вопросы по тегам