В предыдущей статье Как добавить узел в JSON с помощью SQL запроса было рассмотрено, как воспользовавшись средствами самого SQL вставить новый узел в сериализованный JSON объект, хранящийся в ячейке БД. В данной статье рассмотрим, как изменить значение по ключу в JSON объекте, предварительно считав его из другого поля JSON объекта.
DO $$ DECLARE json_data JSONB; DECLARE len INT; DECLARE path text[]; DECLARE base text; DECLAREfilter text; BEGIN -- Читаем существующие JSON данные из поля Configuration SELECT "Configuration" INTO json_data FROM "IdentityIntegration" WHERE "Priority" =1;
-- Вытаскиваем длину узла Servers, который представлет из себя массив json объектов SELECT jsonb_array_length(json_data->'Servers') into len;
-- Для каждого элемента массива Servers FOR i IN0..len-1 LOOP -- формируем путь к ключу path :=ARRAY['Servers', i::text, 'IsReferral']; -- устанавливаем значение json_data по ключу path в false json_data = jsonb_set(json_data, path, 'false'::jsonb, true);
-- считываем значения из json объекта json_data в переменные base и filter SELECT json_data->'Servers'->i->'Sources'->0->'Base'into base; SELECT json_data->'Servers'->i->'Sources'->0->'Filter'intofilter;
-- формируем путь к ключу path :=ARRAY['Servers', i::text, 'GroupSource'];
--RAISE NOTICE 'Iteration: %', ('{"Base": ' || base || ', "Filter": ' || filter || '}'); -- печатаем в консоль если требуется
-- устанавливаем значение json_data по ключу path в сложный объект, получающийся конкатенацией переменных base и filter json_data = jsonb_set(json_data, path, ('{"Base": '|| base ||', "Filter": '||filter||'}')::jsonb, true); END LOOP;
-- Обновляем поле Configuration изменённым значением JSON данных UPDATE "IdentityIntegration" SET "Configuration" = json_data WHERE "Priority" =1; END $$;
COMMIT;
Последний параметр со значения true в функции jsonb_set именуется create_if_missing и является опциональным.
SET@path= CONCAT('$.Servers[', @i, '].GroupSource'); -- применение функции JSON_QUERY(...) к содержимому изменяемого узла удаляет избыточно экранированные кавычки SET@json_data = JSON_MODIFY(@json_data, @path, JSON_QUERY(CONCAT('{"Base": "', @base, '", "Filter": "', @filter, '"}')));
SET@i=@i+1; END;
UPDATE [IdentityIntegration] SET [Configuration] =@json_data WHERE [Priority] =1;
COMMIT; GO
Обращаю внимание на необходимость применения функции JSON_QUERY(...) к содержимому изменяемого узла, иначе вставляемые кавычки будут избыточно экранированы.
Рассмотрим ситуацию, когда в Базе Данных (БД) имеется сохранённый в виде строки сериализованный JSON объект, а нам требуется добавить в него новый узел. Конечно, можно взять язык программирования, считать значение из БД, изменить его и перезаписать в БД. Но хочется воспользоваться средствами самого SQL для подобной операции.
Как это можно сделать в MSSQL:
1 2 3 4 5 6 7 8 9 10 11 12
DECLARE@json NVARCHAR(2000);
-- Читаем существующие JSON данные из поля Configuration SET@json= (SELECT [Configuration] FROM [dbo].[Integration] WHERE [Priority] =1);
-- Изменим JSON данные добавив в них новый узел Cache SET@json= JSON_MODIFY(@json, '$.Cache', JSON_QUERY('{"Interval":1,"IntervalDimension":"day"}'));
-- Обновим поле Configuration изменённым значением JSON данных UPDATE [dbo].[Integration] SET [Configuration] =@json WHERE [Priority] =1;
Обращаю внимание на необходимость применения функции JSON_QUERY(...) к содержимому добавляемого узла, иначе вставляемые кавычки будут избыточно экранированы.
Как это можно сделать в PostgreSQL:
1 2 3 4 5 6 7
DO $$ DECLARE json_data JSONB; BEGIN SELECT "Configuration" INTO json_data FROM "Integration" WHERE "Priority" =1; json_data = jsonb_set(json_data, '{Cache}', '{"Interval": 1, "IntervalDimension": "day"}', true); UPDATE "Integration" SET "Configuration" = json_data WHERE "Priority" =1; END $$;
Последний параметр со значения true в функции jsonb_set именуется create_if_missing и является опциональным.
В случае удаления ненужного узла из JSON объекта требуется установить значение узла в NULL, как показано ниже для MSSQL:
1 2 3 4 5 6 7 8 9
DECLARE@json NVARCHAR(2000);
SET@json= (SELECT [Configuration] FROM [dbo].[Integration] WHERE [Priority] =1);
SET@json= JSON_MODIFY(@json, '$.Cache', NULL);
UPDATE [dbo].[Integration] SET [Configuration] =@json WHERE [Priority] =1;
Начиная с версии .NET6 и выше Microsoft приостанавливает поддержку библиотеки System.Drawing.Common на других платформах, кроме Windows. Это связано с рядом проблем кроссплатформенного портирования GDI+ на другие операционные системы (ОС). В вышеприведённой статье описаны альтернативы System.Drawing.Common, но для версии .NET6 ещё можно продолжать использовать классы из System.Drawing.Common в отличных от Windows ОС.
Для этого:
В проекте, где используются классы из библиотеки System.Drawing.Common, в файле проекта с расширением .csproj (обычно доступны по нажатию клавишы F4 из IDE) требуется добавить такую секцию:
Требуется убедиться, что на ОС установлен пакет libgdiplus. Если пакет отсутствует, то его нужно установить. Для Linux это можно сделать так: sudo apt install libgdiplus
Необходимо учесть, что начиная с .NET7 этот трюк больше не сработает, о чем предупреждается в вышеприведённой статье о Breaking Change от Microsoft.
Давеча выяснил, что мой жёсткий диск забит 7,7 Гигабайтами nuget пакетов. Показалось это перебором и я решил почистить кеш.
Самый простой способ сделать это - с помощью Visual Studio. Для этого перехожу в меню: Tools → Options → NuGet Package Manager → General и нажимаю кнопку Clear All NuGet Storage.
Аналогичную операцию можно выполнить из командной строки, правда для этого понадобится NuGet Commandline. После скачивания перехожу в папку, где расположен nuget.exe .
Чтобы увидеть локальные кэши выполняю команду:
1
nuget locals all -list
Чтобы очистить локальные кэши выполняю команду:
1
nuget locals all -clear
Вот вывод терминала касательно моих кэшей на рабочей машине и процесса их очистки:
Затем повторно оцениваю содержимое категории Other в операционной системе:
Результатом удовлетворён. Заодно почистил неактуальные сырцы (source) и кэш vscode.
При исследовании аудитории пользователей моего проекта для изучения таблицы умножения УмноДел выяснилось, что кроме русскоязычных пользователей, заходит немалое количество пользователей из других стран. Это подтолкнуло меня на поиски быстрого способа добавления переключателя языков (RU, EN) в моё Angular приложение. Очень большое подспорье оказала статья How to translate your Angular app with ngx-translate, только с BabelEdit мне подружиться не удалось и многие пункты из статьи я просто выполнил вручную. В итоге всё успешно получилось и без него.
Только одна заковыка меня поджидала с элементом <mat-select>. При первоначальной загрузке сайта его значение по умолчанию не отображалось и создавалось впечатление, что значение не установлено, хотя это не так.
В конструкторе app.component.ts значение было установлено selectedOperations = 'multiply'; , а при клике по элементу видно, что этот пункт уже выбран. При последующих переключениях языка select сразу устанавливался правильно и я начал подозревать, что проблема связана с корректной подгрузкой перевода ngx-translate при первоначальной загрузке. Какое-либо действие с элементом, либо переключение языков приводило сразу же к корректному отображению значения элемента!
Стоит отметить, что ngx-translate динамически подгружает переводы значений и в некоторых случаях могут вместо значений переводов отображаться ключи словарей, пока эти словари подгружаются. Это именуется глюками от использования TranslateLoader (glitches when using TranslateLoader) и решение в борьбе с ними заключается в предварительной подгрузке переводов из json-файла при первоначальной загрузке приложения. Я использовал аналогичное решение, чтобы подгрузить значения предварительно, а не только динамически.
Для этого в требуется включить возможность загрузки json-файлов в tsconfig.app.json:
При переходе с .NET 5 на .NET 6 столкнулся с такой проблемой: EF Core отказался строить сложные IQueryable запросы LINQ в .NET 6, с которыми ранее справлялся в .NET 5 без проблем. Поиски ответа на вопрос, что же случилось, привели к следующему решению.
При использовании .NET 5 и ниже использование метода Contains для проверки содержится ли значение в множестве других значений препокойно транслировалось в SQL выражение вида WHERE City IN ('Paris','London'), но начиная с версии .NET 6 метод Contains не может как раньше транслироваться в SQL и падает с исключением, предлагающим упростить выражение или переписать его через другие операторы.
Таким образом, такой код больше не работает в .NET 6:
1 2
var ids = new List<int> { 1, 2, 3 }; var entities = dbContext.Posts.Where(p => ids.Contains(p.Id));
Решение заключается в использовании метода Any вместо Contains для проверки содержится ли значение в множестве других значений:
1 2
var ids = new List<int> { 1, 2, 3 }; var entities = dbContext.Posts.Where(p => ids.Any(id => id == p.Id));
Вот такое LINQ выражение .NET 6 уже принимает спокойно и EF Core транслирует в ту же самую конструкцию SQL вида WHERE Id IN (1,2,3).
Переписав все конструкции содержащие ids.Contains в ids.Any всё стало работать как и прежде, и задача была успешно решена.
Хотел бы отметить, что данная проблема касается только выборки из множества значений. String.Contains данная проблема не коснулась и он нормально транслируется в конструкцию SQL вида LIKE.
У меня возникла потребность для HttpGet запроса проверять, что в качестве QueryString параметра запроса прислан не просто какой-то там string, а что присланое значение успешно преобразуется к перечислению Enum вида:
1 2 3 4 5
publicenum QrCodeTypeEnum { Location, Device }
Причём если QueryString параметр “левый”, или вообще не прислан, то не должно самопроизвольно выбираться дефолтное значение перечисления QrCodeTypeEnum.Location как при обычном [FromQuery] аттрибуте (тут даже аттрибут [BindRequired] не поможет).
Для решения данной задачи потребовалось расширить возможности FromQueryAttribute и реализовать интерфейс IParameterModelConvention, который обязует имплементировать метод void Apply(ParameterModel parameter).
При инициализации контроллера, содержащего наш атрибут [RequiredFromQuery], происходит вызов метода Apply, который понимает, что аттрибут применён к перечислению, благодаря свойству parameter.ParameterType.IsEnum, и формирует предикат, который передаётся конструктору RequiredFromQueryActionConstraint и будет использован при поступлении запроса в контроллер для проверки поступающего значения в QueryString.
Давайте взглянем как реализован класс RequiredFromQueryActionConstraint, который имплементирует интерфейс IActionConstraint:
publicboolAccept(ActionConstraintContext context) { if (!context.RouteContext.HttpContext.Request.Query.ContainsKey(_parameterKey)) { returnfalse; }
if (!context.RouteContext.HttpContext.Request.Query.TryGetValue(_parameterKey, outvarvalue)) { returnfalse; } if (_rejectionPredicate != null && _rejectionPredicate.Invoke(value.ToString())) { returnfalse; }
returntrue; } } }
При поступлении HttpGet запроса вида /v3/device/download-report?type=device к контроллеру device, вызывается метод Accept класса RequiredFromQueryActionConstraint , который проверяет наличие ключа type, наличие значения у этого ключа, и что значение ключа соответствует предикату _rejectionPredicate, который формируется из значений перечисления в методе Apply атрибута RequiredFromQueryAttribute. Теперь проверка QueryString параметра запроса осуществляется строго, и в случае отсутствия ключа type, либо неверного (отсутствующего в перечислении) значения ключа type методом будет дан ответ с кодом 400 и сообщением вида:
1 2 3 4 5
{ "id": [ "The value 'download-report' is not valid." ] }
Использовать атрибут [RequiredFromQuery] можно в методе контроллера следующим образом:
1 2 3 4 5 6
[HttpGet("download-report")] [Authorize(Policy = PolicyNameProvider.AuthenticatedIdentityIsCurrentAccountAdmin)] public FileStreamResult DownloadAccountQrCodesReport([RequiredFromQuery] QrCodeTypeEnum type) { var qrCodeType = type.ToQrCodeType(); ... и так далее
Для восстановления базы данных из файла бэкапа в PostgreSQL есть несколько методов, но если размер файла с “дампом” очень велик, то восстановить базу можно только из консоли.
Для этого открываем PowerShell от имени администратора и выполняем следующую команду вида:
1
psql -U username -d database_name -f backup.sql
, где username - имя пользователя базы данных, database_name - имя базы данных, backup.sql - имя файла с бэкапом базы данных.
При создании Базы Данных (БД) в MS SQL Server для оптимизации логирования БД требуется установить Recovery Mode в значение Simple.
Для уже созданной БД с режимом Recovery Mode = Full требуется перейти в свойства БД и в разделе Options перевести соответствующую настройку из значения Full:
в значение Simple:
Затем необходимо очистить лишние записи действий в файле логов, т.к. модель восстановления данных Recovery Mode изменилась.
Для этого выберите требующуюся БД, затем кликните по ней Правой Кнопкой Мыши (ПКМ) и выберите Tasks → Shrink → Files:
После этого в появившемся окне формы Shrink File требуется установить File type: Log и выбрать Release unused space
Текущий размер занимаемого дискового пространства логами и свободное место для них можно оценить с помощью параметров Currently allocated space и Available free space этой же формы Shrink File (при повторном её открытии можно увидеть размеры после применения операции сжатия файла логов)
Попросили меня друзья помочь с настройкой CreateReactApp (CRA) приложения, чтобы синтаксис проверялся, красота кода в одном стиле автоматически наводилась, и чтобы не давало закоммитить, если всё вышесказанное не выполняется.
Начал я с настройки ESLint. Он нужен для проверки синтаксиса, стиля кодирования и поиска ошибок в коде. По умолчанию ESLint уже есть в CRA, его нужно только инициализировать через консоль в папке проекта:
1
yarn eslint --init
И затем настроить. Установите через консоль следующие плагины в папку проекта:
Из конфига ESLint видно, что мы используем AirBnb, а также настраиваем сразу и Prettier. Prettier - это инструмент автоматического форматирования кода. Для его установки запустите в консоли:
Теперь можно проверить что всё впорядке, запустив в консоли следующие команды:
1 2
yarn lint yarn format
Если вы настроили всё правильно команды отработают без ошибок.
Добавим теперь заключительную вещь: чтобы при попытке коммита в git запускались наши вышеуказанные скрипты, и можно было закоммитить только код, который эти скрипты успешно проходит.
Для этого установим Husky, который будет реагировать на событие попытки коммита, следующей командой из консоли:
1
yarn add -D lint-staged husky
Для инициализации Husky выполните в консоли команду:
1
yarn husky install
Так как у нас версия Husky 7, то в соответствующей скрытой папке .husky необходимо внести следующие изменения в файл pre-commit:
1 2 3 4 5
#!/bin/sh . "$(dirname "$0")/_/husky.sh"
yarn lint yarn format
Теперь вам удастся закоммитить ваш код только в случае успешного выполнения скриптов из файла pre-commit.