» » Доступ к свойствам внутри поля Jsonb для Npgsql

 

Доступ к свойствам внутри поля Jsonb для Npgsql

Автор: admin от 15-08-2019, 17:30, посмотрело: 1172

PostgreSQL имеет тип данных Jsonb, который позволяет добавлять к стандартной реляционной модели дополнительные свойства с возможностью поиска по ним.



EntityFramework Core с расширением Npgsql умеет вытягивать данные поля в тип System.String



Однако для фильтрации по Json свойствам через EF на уровне запросов приходится использовать чистый SQL, что не очень то удобно, так как нужно лезть в маппинг (если он не автоматический), искать названия полей, соответствующих свойствам моделей, поддерживать это именование. Пропадает гибкость, которую нам дает ORM.



Если вас это угнетает, так же как и меня, добро пожаловать под кат.



В конце статьи имеется ссылка на исходники!

Npgsql.Json.NET, который умеет проецировать Json и Jsonb значения в CLR типы. Если честно, не понимаю, для чего он может понадобиться, ведь раз у нас появилась необходимость в Json поле в реляционной БД, вероятнее всего у нас есть сущности с динамическим набором полей.



Алгоритм решения задачи




  • Определить метод (или методы), который будет покрывать наши потребности.

  • Создать транслятор, который будет участвовать в генерации SQL кода.

  • Прикрутить все это к Npgsql.



  • Решение



    Для начала определим метод. Хочу, чтобы он использовался как то так:



    context.Entity.Where(x => JsonbMethods.Value<string>(x.JsonbField, "jsonPropertyName") == "value")


    Следовательно, вот наш метод:



    public static TSource Value<TSource>(object jsonbProperty, string jsonbPropertyName)
    {
        throw new NotSupportedException();
    }


    Я несколько часов ковырял исходники EF Core, Npgsql и не только в поисках способов расширить базовый функционал генерации SQL. Добрался вот до этой статьи, но подход автора по способу подключения транслятора мне не понравился, ведь он переопределяет стандартный инструмент, а значит может конфликтовать с другим похожим инструментом.

    В итоге добрался до исходников Net Topology Suite. Все, что мне оттуда потребовалось, это способ подключения транслятора методов.



    Но больше всего времени я потратил на то, чтобы сформировать нужный мне фрагмент sql.



    Вот требуемый синтаксис



    tableAlias."JsonField"-"insideProperty"



    Сначала я пробовал в трансляторе возвращать ColumnExpression. Первым параметром при его создании идет имя столбца (string). Я просто состряпал его из параметров, которые мне приходят в метод. Запустил, проверил, ошибка. Оказывается, то, что я передаю в качестве имени, оборачивается в кавычки. В итоге SQLполучился таким tableAlias.""JsonField"-"insideProperty"".



    В исходниках генератора я нашел метод VisitColumn, в котором это поведение было хардкодным и не зависело ни от каких параметров. То есть я не мог на это повлиять. Нужно было искать другое решение.



    Тогда я создал собственный Expression — JsonbPropertyAccessorExpression: Expression



    Осталось переопределить его метод Accept для ISqlExpressionVisitor.



    Но вот беда, в данном интерфейсе нет метода, который бы мог сегнерировать кастомный оператор. Тогда мне пришла в голову мысль посетить не один метод, а несколько. Посетил сначала VisitColumn, который создал доступ к столбцу tableAlias.«JsonField», затем VisitSqlFragment, в который я прокинул "-'insideFieldName'".



    Я и не надеялся, но все заработало. Почти.



    Когда я пытался фильтровать по тексту по точному совпадению почему то формировался такой фильтр tableAlias."JsonField"-"insideProperty" = JSONB "value", что вызывало ошибку, так как привести текст к типу JSONB нельзя, если там не содержится валидный Json. Да и зачем мне что-то к чему-то приводить, когда я хочу текст?



    Я было даже принял решение из маппинга модели убрать пометку со столбца Jsonb, что это Jsonb, добавив только эту пометку в MigrationContext, чтобы он генерировал правильные миграции. И это даже взлетело, но подход показался мне костыльным. Тем не менее на этом я и остановился.



    После этого я принялся за CAST, ведь метод Value у меня универсальный и в Json свойствах могут быть различные типы данных, по которым тоже нужно сортировать и фильтровать.



    В итоге из моего транслятора я стал возвращать ExplicitCastExpression, в который передавал свой кастомный Expression и тип, который содержался в универсальных аргументах метода Value.



    когда я посмотрел на получившийся SQL при поиске по дате, я обнаружил, что к сравниваемое значение приводится к типу timestamp. timestamp 'some date value'. И тут до меня дошло. Предыдущая проблема, которую я решил костылем, ушла сама собой. Когда аксессор к полю Json кастился в текст, генератор больше не добавлял явное преобразование в JSONB, ведь слева операции сравнения уже был текст, а по умолчанию аксессор поля Jsonb возвращает тип Jsonb.



    В завершении



    В завершении хочу добавить, что я не нашел документации по тому, как мне добавить кастомные трансляторы свойств и методов. наверное, плохо искал. Если у кого-то есть замечания по подходу, по коду и тд, пишите в комментариях.



    Если кто-нибудь захочет расширить библиотеку в форках, пишите в личку, я постараюсь помочь. Ну или кидайте пулреквесты.



    Вот ссылка на исходники



    Источник: Хабр / Интересные публикации

    Категория: Программирование

    Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
    Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

    Добавление комментария

    Имя:*
    E-Mail:
    Комментарий:
    Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
    Введите два слова, показанных на изображении: *