FastSqliteHelper — Легковесный C# wrapper для упрощения работы с SQLite в ZennoPoster

Lord_Alfred

Client
Регистрация
09.10.2015
Сообщения
3 916
Благодарностей
3 867
Баллы
113
Небольшая библиотека для упрощения работы с SQLite базой данных в ZennoPoster.

В первую очередь, данная библиотека поможет исключить множество повторяющегося кода (перехват ошибок, создание SQLiteCommand на каждый запрос, правильную передачу параметров через SQLiteCommand.Parameters). А также, облегчит разработку и упростит обновление и изменение ваших проектов по ходу их развития.

Идея и часть реализации подсмотрена у @DmitryAk + PHP PDO database framework Medoo.
Текущий релиз не избавляет полностью от работы с System.Data.SQLite объектами + содержит несколько изъянов, которые могут допустить SQL-injection (или скорее порчу данных), но даже эта версия уже пригодна для использования (аккуратного).

Установка
  1. Скомпилировать самостоятельно или взять последнюю версию FastSqliteHelper.dll из релизов и положить в директорию:
    Код:
    C:\Program Files (x86)\ZennoLab\RU\ZennoPoster Pro\[версия ZP]\Progs\ExternalAssemblies
  2. Скопировать System.Data.SQLite.dll и SQLite.Interop.dll из папки sqlite-netFx46-binary-x64-2015-1.0.107.0 (или скачать из последнего релиза) в вышеприведенную директорию ExternalAssemblies
  3. В проекте выбрать Добавить действие -> Свой код -> Ссылки из GAC
  4. Зайти в появившийся внизу блок References (в Расширенном редакторе), нажать кнопку Добавить..., затем кнопку Обзор...
  5. В появившемся окне выбрать:
    Код:
    C:\Program Files (x86)\ZennoLab\RU\ZennoPoster Pro\[версия ZP]\Progs\ExternalAssemblies\FastSqliteHelper.dll
  6. Повторить пункт 4 и в появившемся окне выбрать:
    Код:
    C:\Program Files (x86)\ZennoLab\RU\ZennoPoster Pro\[версия ZP]\Progs\ExternalAssemblies\System.Data.SQLite.dll
  7. В проекте выбрать Добавить действие -> Свой код -> Директивы using и общий код
  8. Зайти в появившийся внизу блок OwnCodeUsings (в Расширенном редакторе) и в окне "Директивы Using" вставить:
    C#:
    using FastSqliteHelperLib;
    using System.Data.SQLite;
После этого у вас в коде появится класс FastSqliteHelper с публичными методами, описанными ниже.

Советы по использованию
  • Примеры работы практически со всеми методами есть в шаблоне test_project.xmlz
  • При использовании многопоточных шаблонов в ZennoPoster всегда добавляйте к строке дополнительных параметров connection string в методе FastSqliteHelper.Init включение типа журнала WAL с помощью:
    Код:
    Version=3;Journal Mode=WAL;
    С ним скорее всего станет возможно писать и читать из одной таблицы без получения состояния "database is locked" в многопотоке. Но появятся дополнительные файлы (*-shm и *-wal), при этом вам нужно будет решить: оставить фиксацию измененных данных в автоматическом режиме или вручную делать так называемый "checkpoint" (везде советуют делать его в отдельном процессе, когда работа с базой в этот момент сведена к минимуму).
  • Всегда закрывайте соединение с базой данных через метод FastSqliteHelper.DeInit.
  • Если в результате выполнения какого-либо из методов в результате возвращается объект класса System.Data.SQLite.SQLiteDataReader ("читатель"), то необходимо всегда закрывать его через метод .Close, чтобы избежать ошибок в многопотоке.
    Пример корректного закрытия "читателя" при получения множества строк с помощью метода FastSqliteHelper.Select:
    C#:
    System.Data.SQLite.SQLiteDataReader reader = FastSqliteHelper.Select("table_name", new string[]{"id", "value"});
    try {
        foreach(dynamic row in reader) {
            project.SendInfoToLog("Строка: " + row["id"] + " -> " + row["value"]);
        }
    } finally {
        reader.Close();
    }
    Пример корректного закрытия "читателя" при получении единственного поля из единственной строки с помощью метода FastSqliteHelper.Select:
    C#:
    System.Data.SQLite.SQLiteDataReader reader = FastSqliteHelper.Select("table_name", "count(id) as count_all");
    int count = 0;
    try {
        if (reader.Read()) {
            count = Convert.ToInt32(reader["count_all"]);
        }
    } finally {
        reader.Close();
    }
  • Если в каком-то условии (condition) для выборки/обновления/удаления вам нужно сделать LIMIT, ORDER BY или что-то, что идет за WHERE - не забывайте, что в библиотеке это условие конкатенуется ("склеивается") с:
    Код:
    WHERE 1=1 and
    Поэтому, если вам не нужно указывать критерий для выборки (WHERE) - лучше добавить в условие что-то вроде (пример для LIMIT):
    Код:
    2=2 LIMIT 0,10
    Для того, чтобы избежать ошибки syntax error.
  • Для массового добавления строк в таблицу необходимо открывать транзакцию (пример реализации работы через транзакцию есть в методе FastSqliteHelper.Insert, но он может быть неудобен для ваших задач, т.к. предварительно нужно будет сделать список словарей с данными для вставки). Если не открывать транзакцию, то SQLite сделает это неявно на каждый Insert, поэтому у вас с очень большой долей вероятности будут "тормоза". Также, для ускорения процесса вставки (только при использовании в однопоточном шаблоне!) лучше отключить тип журнала и тип синхронизации при подключении к БД:
    Код:
    Journal Mode=OFF;Synchronous=0;
  • В SQLite при дефолтном "journal_mode" очень просто поймать состояние "database is locked", оно возникает в том случае, если первый поток читает данные из таблицы, а второй поток туда пишет. Чтобы предотвратить такого рода поведение - нужно заранее продумывать логику работы шаблона и обязательно использовать оператор блокировки:
    C#:
    lock(YourOwn.LockerObject) {
    // работа с БД через методы FastSqliteHelper
    }
    Плюс, как было написано в самом начале - лучше использовать тип журнала WAL, но не факт, что это даст 100% гарантии того, что шаблон не попадет в такое состояние.
  • Всегда явно указывайте столбцы для выборки, избегайте "*" для возвращения всех столбцов из таблицы. Это ускорит работу и поможет избежать ошибок, допущенных по невнимательности.
  • В методе подключения к БД есть один очень важный параметр: throw_exc_on_errors - "выдавать ли исключение в случае ошибки или просто по-тихому писать в лог ZP". Я настоятельно рекомендую выставлять его всегда в true, чтобы в случае ошибки - шаблон останавливал свое выполнение и прекращал работу, минуя тем самым другие ошибки, которые могут произойти далее.
  • К сожалению, в текущей реализации невозможно работать с 2 и более более SQLite базами данных одновременно. Это ограничение связано с тем, что класс FastSqliteHelper и его методы - статические. Можно сделать "хак" с monkey-patching объекта подключения и переопределять его "на лету", но это не тот подход, который хотелось бы предлагать для использования. Возможно, когда-то в дальнейшем эта оплошность будет устранена.
  • Для удобного просмотра/изменения базы данных я советую использовать бесплатную программу SQLiteStudio (в ней есть русификация).
  • Помните, что SQLite в первую очередь - это встраиваемая база данных в одном файле, поэтому ожидать от неё существенного прироста производительности - не стоит. Для таких целей лучше использовать MySQL или PostgreSQL. Но в целом, для небольших проектов/шаблонов, в которых не будет миллионов строк в базе - это очень хорошее решение, которое поможет избавиться от "списков" и "таблиц" в ZennoPoster.
    Советую к прочтению небольшой цикл статей о SQLite на хабре.

Описание методов FastSqliteHelper
  • FastSqliteHelper.Init — Инициализация подключения к SQLite базе данных.
  • FastSqliteHelper.Init — Инициализация подключения к SQLite базе данных (дополнительные параметры connection string в виде словаря).
  • FastSqliteHelper.DeInit — Закрытие подключения к базе данных.
  • FastSqliteHelper.PragmaSet — Установка параметра PRAGMA для текущего подключения к БД.
  • FastSqliteHelper.PragmaGet — Чтение параметра PRAGMA из текущего подключения к БД.
  • FastSqliteHelper.Select — Получение данных из таблицы по условию (столбцы в виде строки).
  • FastSqliteHelper.Select — Получение данных из таблицы по условию (столбцы в виде массива строк).
  • FastSqliteHelper.Select — Получение данных из таблицы по условию (столбцы в виде списка).
  • FastSqliteHelper.Insert — Добавление одной записи в таблицу.
  • FastSqliteHelper.Insert — Добавление множества записей в таблицу (используя транзакцию на все добавляемые данные).
  • FastSqliteHelper.LastInsertID — Идентификатор последней добавленной записи.
  • FastSqliteHelper.Update — Обновление значений в таблице по указанному условию.
  • FastSqliteHelper.Update — Обновление значения одного столбца в таблице по указанному условию.
  • FastSqliteHelper.Delete — Удаление записей из таблицы по указанному условию в виде строки.
  • FastSqliteHelper.Delete — Удаление записей из таблицы по указанному условию в виде словаря.
  • FastSqliteHelper.Query — Любой запрос к БД, возвращающий количество затронутых строк.
  • FastSqliteHelper.QueryReader — Любой запрос к БД, возвращающий объект SQLiteDataReader с содержимым.
  • FastSqliteHelper.QueryScalar — Любой запрос к БД, возвращающий первый столбец первой строки.
  • FastSqliteHelper.GetLastQuery — Получить последний отправленный запрос (включая запросы, в ходе обработки которых возникла ошибка).
С полным описанием методов (их параметрами и возвращаемыми значениями) вы можете ознакомиться на странице в github-репозитории.

TODO

Ниже описан план развития, задачи из которого по моему мнению нужно сделать в первую очередь, минуя добавление другого функционала, который может быть реализован непосредственно через работу с System.Data.SQLite.
Если у вас есть непреодолимое желание помочь этому проекту в развитии - посмотрите ниже, что я хотел бы видеть в следующих версиях этой библиотеки, сделайте Fork репозитория и отправьте свой Pull Request с решением любой из задач :-)

  • Написать тесты для каждого метода, чтобы в случае выпуска нового релиза можно было проверить не сломалось ли что-то где-то.
  • Реализовать тестовый проект для консольного приложения, чтобы не было нужды проверять все методы в ProjectMaker, а была возможность работы в VisualStudio/SharpDevelop.
  • Проверить и разобраться почему при установке какого-либо значения PRAGMA через строку подключения - чтение этого параметра через метод PragmaGet получает другой результат.
  • Избавиться от возвращения SQLiteDataReader в нескольких методах, чтобы можно было не думать о том, что его нужно всегда "закрывать".
  • Облегчить работу с транзакциями (избавиться от явного вызова Commit/Rollback), используя анонимные функции (делегаты в C#?).
    Пример правильной организации выполнения запросов внутри транзакции на PHP: https://medoo.in/api/action
  • Реализовать передачу условий в методы, чтобы они не "склеивались" с условием WHERE 1=1 and, чтобы была возможность передать LIMIT 0,10 без добавления конструкции вида 2=2.
  • Продумать и реализовать способ передачи условий в виде "столбец" => "значение" без ограничения на то, что все имена столбцов должны быть уникальны.
  • Добавить метод для экранирования значений, которые могут быть в дальнейшем переданы в виде plain-запроса в методы Query*.
    Пример похожего функционала в PHP PDO драйвере: http://php.net/manual/ru/pdo.quote.php
  • Добавить возможность работы с несколькими SQLite базами одновременно (убрать статический модификатор для класса, но это повлечет проблемы при передаче объекта через project.Context в ProjectMaker или сделать поле connection в виде словаря подключений, но нужно будет предварительно протестировать этот вариант в многопоточном режиме, чтоб не вышло, что данные будут пересекаться).
  • Переименовать методы Init -> Open, DeInit -> Close. Сомнительно и добавит обратную несовместимость, но читаемость кода повысится.
  • Реализовать получение/вставку/обновление/удаление данных через объекты, а не через словари. Плюс - не нужно будет делать некрасивые словари для работы с этими методами; минус - для каждой таблицы, с которой нужно будет работать - нужно будет создавать классы и вести работу через них.
  • Проверить и исправить все методы, которые подвержены sql-injection, чтобы в ходе работы не испортились данные.
  • В методах, которые не требуют работы с параметрами (SQLiteCommand.Parameters) - переписать отправку запроса/получение результата через внутренние методы Query*. Но это может привести к тому, что будет сложно разобрать в сообщении об ошибке - откуда именно упало исключение (поэтому данная задача под сомнением).
  • Добавить метод, который будет возвращать последний выполненный запрос (или даже лучше - отправленный, чтобы в случае перехвата ошибки внутри метода его можно было отобразить).
    Пример такого функционала в PHP фреймворке Medoo: https://medoo.in/api/last
  • Подумать над тем, что в последний отправленный запрос может быть списком (логом) всех запросов. Плюс решить стоит ли там заменять параметры на их значения.
  • Написать Contributing Guidelines, которые будут включать в себя: правильную настройку редактора, чтобы избежать табуляций в коде; использование String.Format и String.Concat; удаление trailing whitespace и другие полезные вещи.

Послесловие

В прикрепленных ниже файлах вы найдете архив со скомпилированными библиотеками и тестовый шаблон с примерами работы практически всех методов. Вся дополнительная документация и весь исходный код опубликованы в github-репозитории: https://github.com/lord-alfred/FastSqliteHelper

На самом деле, изначально я отправлял заявку на конкурс для совсем другого шаблона, но в ходе его написания появилась острая необходимость в таком легковесном SQLite C# wrapper, чтобы было удобнее манипулировать данными. Но это уже совсем другая история...)
 
Категория
Полезно
Номер конкурса шаблонов
Первый конкурс шаблонов
Уровень сложности
Продвинутый

Вложения

Для запуска проектов требуется программа ZennoPoster или ZennoDroid.
Это основное приложение, предназначенное для выполнения автоматизированных шаблонов действий (ботов).
Подробнее...

Для того чтобы запустить шаблон, откройте нужную программу. Нажмите кнопку «Добавить», и выберите файл проекта, который хотите запустить.
Подробнее о том, где и как выполняется проект.

Последнее редактирование:

Troll_UA

Client
Регистрация
11.04.2016
Сообщения
121
Благодарностей
38
Баллы
28
Как раз вовремя подкатило. Делаю проект и ломал голову какую базу использовать, SQLite думаю как раз то что надо. У Lord_Alfred как всегда годные решения.
 
  • Спасибо
Реакции: Lord_Alfred

Lord_Alfred

Client
Регистрация
09.10.2015
Сообщения
3 916
Благодарностей
3 867
Баллы
113
Я немного накосячил с описанием типа журнала подключения WAL, поэтому исправил все примеры + тестовый шаблон и обновил пост (и README на github).
Плюс к этому, сделал пару улучшений :-)
Поэтому выпускаю релиз с версией v1.1

История изменений:
  • Добавлен метод GetLastQuery (последний отправленный запрос) + добавлено его описание в readme
  • В описании ошибок, которые генерируются из библиотеки, добавлен вывод последнего отправленного запроса
  • Доступ к методам GenerateErrorMessage и SendToLog изменен на приватный
  • Улучшена простановка connection string для подключения + в случае ошибки добавлено отображение connection string в описании ошибки
  • Исправлено неправильное описание подключения с использованием типа журнала WAL + исправлен тестовый шаблон

В первом посте архив библиотек и тестовый шаблон обновлены, поэтому там сбросились количество скачиваний.
 
  • Спасибо
Реакции: Lanidor и Yuriy Zymlex

Lord_Alfred

Client
Регистрация
09.10.2015
Сообщения
3 916
Благодарностей
3 867
Баллы
113
  • Спасибо
Реакции: budora

lzlmrf

Client
Регистрация
14.08.2015
Сообщения
488
Благодарностей
149
Баллы
43
Если в каком-то условии (condition) для выборки/обновления/удаления вам нужно сделать LIMIT, ORDER BY или что-то, что идет за WHERE - не забывайте, что в библиотеке это условие конкатенуется ("склеивается") с:
WHERE 1=1 and
Можно подробнее пояснить этот момент? в тестовом шабе не нашел пример выборки по условиям.
как допустим это написать SELECT * FROM test ORDER BY date WHERE name=NOT NULL LIMIT 1 ;
 

Lord_Alfred

Client
Регистрация
09.10.2015
Сообщения
3 916
Благодарностей
3 867
Баллы
113
SELECT * FROM test ORDER BY date WHERE name=NOT NULL LIMIT 1 ;
Только корректнее писать (по крайней мере в MySQL):
SQL:
SELECT * FROM test WHERE name=NOT NULL ORDER BY date LIMIT 1;


Но в том процитированном тексте речь идет о конструкции вида:
SQL:
SELECT * FROM test ORDER BY date LIMIT 1;


То есть без указания условий выборки. И в FastSqliteHelper это нужно будет записать следующим образом:
C#:
FastSqliteHelper.Select("test", "*", "2=2 ORDER BY date LIMIT 1");

Откуда взялось и зачем нужно "2=2"? Об этом можно прочитать как раз в том месте в первом посте, которое процитировано :-)
Если убрать это, то FastSqliteHelper выдаст ошибку:
[FastSqliteHelper.Select]: 'Ошибка: SQL logic error
near "ORDER": syntax error'. Last query: 'SELECT * FROM test WHERE 1=1 and ORDER BY date LIMIT 1'
 
  • Спасибо
Реакции: lzlmrf

chazzzzz

Client
Регистрация
08.07.2017
Сообщения
63
Благодарностей
9
Баллы
8
Привет! Спасибо за библиотеку!
Есть такая проблема, почему-то на домашнем пк win 10 проект с библиотекой работает исправно, а на серваке с win 2012 возникает проблема в проекте на этапе SELECT по условию, ничего не находит, при этом к базе подключается, тк появляются записи журнала. Запись в базу тоже работает исправно. Не работает только SELECT по условию.
Есть какие-то идеи, в чем может быть проблема?
 

Lord_Alfred

Client
Регистрация
09.10.2015
Сообщения
3 916
Благодарностей
3 867
Баллы
113

Astraport

Client
Регистрация
01.05.2015
Сообщения
4 983
Благодарностей
4 433
Баллы
113
Можно подробнее пояснить этот момент? в тестовом шабе не нашел пример выборки по условиям.
как допустим это написать SELECT * FROM test ORDER BY date WHERE name=NOT NULL LIMIT 1 ;
5 раз перечитал, но так и не понял как работать с селектом с условием WHERE как и у процитированного коллеги.
1=1, 2=2 - что это такое и как мне добавить нормальное условие?
 

Lord_Alfred

Client
Регистрация
09.10.2015
Сообщения
3 916
Благодарностей
3 867
Баллы
113
5 раз перечитал, но так и не понял как работать с селектом с условием WHERE как и у процитированного коллеги.
Там вроде был пример в архиве с такими условиями)

1=1, 2=2 - что это такое и как мне добавить нормальное условие?
Это дополнительные условия, в результате которых всегда будет результат "true" (истина), они нужны для того, чтобы использовать конкатенацию (в нашем случае "and") в условии WHERE. Как простым языком это объяснить - даже не знаю. Суть в том, что если не добавить это условие, то будет некорректно сформированный запрос отправляться в базу и будет ошибка вылетать. Нельзя просто так взять и передать в аргументе что-то, что может идти за блоком WHERE без добавления условия в духе "2=2", т.к. перед вашим передаваемым параметром - будет "and".
 
  • Спасибо
Реакции: Astraport

doc

Client
Регистрация
30.03.2012
Сообщения
8 684
Благодарностей
4 641
Баллы
113
Там вроде был пример в архиве с такими условиями)


Это дополнительные условия, в результате которых всегда будет результат "true" (истина), они нужны для того, чтобы использовать конкатенацию (в нашем случае "and") в условии WHERE. Как простым языком это объяснить - даже не знаю. Суть в том, что если не добавить это условие, то будет некорректно сформированный запрос отправляться в базу и будет ошибка вылетать. Нельзя просто так взять и передать в аргументе что-то, что может идти за блоком WHERE без добавления условия в духе "2=2", т.к. перед вашим передаваемым параметром - будет "and".
Простым языком - необходимый костыль)
 
  • Спасибо
Реакции: Lord_Alfred

Astraport

Client
Регистрация
01.05.2015
Сообщения
4 983
Благодарностей
4 433
Баллы
113
Это дополнительные условия, в результате которых всегда будет результат "true" (истина), они нужны для того, чтобы использовать конкатенацию (в нашем случае "and") в условии WHERE. Как простым языком это объяснить - даже не знаю. Суть в том, что если не добавить это условие, то будет некорректно сформированный запрос отправляться в базу и будет ошибка вылетать. Нельзя просто так взять и передать в аргументе что-то, что может идти за блоком WHERE без добавления условия в духе "2=2", т.к. перед вашим передаваемым параметром - будет "and".
Это я понял. То есть например выбрать все ряды где столбец = 45 у меня не получится? Значит единственный выход писать в запросе 2=2 и забирать всю таблицу и в цикле перебирать с условием if, так?
 

Lord_Alfred

Client
Регистрация
09.10.2015
Сообщения
3 916
Благодарностей
3 867
Баллы
113
Это я понял. То есть например выбрать все ряды где столбец = 45 у меня не получится? Значит единственный выход писать в запросе 2=2 и забирать всю таблицу и в цикле перебирать с условием if, так?
Всё с точностью до наоборот.
Всё можно, никаких циклов не нужно, 2=2 нужно писать для того, чтоб добавить любой блок после WHERE без условий



Если в каком-то условии (condition) для выборки/обновления/удаления вам нужно сделать LIMIT, ORDER BY или что-то, что идет за WHERE - не забывайте, что в библиотеке это условие конкатенуется ("склеивается") с:
Код:
WHERE 1=1 and
Поэтому, если вам не нужно указывать критерий для выборки (WHERE) - лучше добавить в условие что-то вроде (пример для LIMIT):
Код:
2=2 LIMIT 0,10
Для того, чтобы избежать ошибки syntax error.
 
  • Спасибо
Реакции: Astraport

Astraport

Client
Регистрация
01.05.2015
Сообщения
4 983
Благодарностей
4 433
Баллы
113
Всё с точностью до наоборот.
Всё можно, никаких циклов не нужно, 2=2 нужно писать для того, чтоб добавить любой блок после WHERE без условий
Спасибо, теперь надеюсь что разберусь.
Но пришла новая беда.
На локальном компе все отлично, но пытаюсь собрать на сервере в ЗенноБокс и постоянно получаю:
Код:
Perform the CSharp OwnCode  [FastSqliteHelper.Init]: 'Ошибка подключения к БД: Unable to load DLL 'SQLite.Interop.dll': The specified module could not be found. (Exception from HRESULT: 0x8007007E)'. Last query: ''. Строка подключения: Data Source=c:\users\administrator\documents\zennolab\zennobox\purchasedproducts\ваввава@mail.ru\db/db.db;Version=3;Journal Mode=WAL;
Либы все три при публикации упаковал, на всякий случай руками добавил в ExternalAssemblies.
Что ещё нужно?
 

Yuriy Zymlex

Moderator
Команда форума
Регистрация
24.10.2016
Сообщения
6 518
Благодарностей
3 370
Баллы
113
На локальном компе все отлично, но пытаюсь собрать на сервере в ЗенноБокс и постоянно получаю
Попробуй SQLite с Nuget скачать, там в некоторых версиях такая фигня.
Файл распакуй архиватором.
 
  • Спасибо
Реакции: Astraport

Bullet

Client
Регистрация
16.12.2015
Сообщения
186
Благодарностей
19
Баллы
18
Други, помогите разобраться. Есть такой код:

Код:
    System.Data.SQLite.SQLiteDataReader read_fields = FastSqliteHelper.Select("query", "region, district", "status LIKE 'Новый' LIMIT 1");
    try {
        if (read_fields.Read()) {
            project.Variables["region"].Value = read_fields["region"];
        }
    } finally {
            read_fields.Close();
Спотыкается на записи значения в переменную. Что я не так сделал?
 

Lord_Alfred

Client
Регистрация
09.10.2015
Сообщения
3 916
Благодарностей
3 867
Баллы
113
Спотыкается на записи значения в переменную. Что я не так сделал?
Скорее всего дело в том, что нужно приводить тип данных к строковому типу.
Попробуйте вот так сделать:
Код:
project.Variables["region"].Value = Convert.ToString(read_fields["region"]);
 
  • Спасибо
Реакции: Bullet

Bullet

Client
Регистрация
16.12.2015
Сообщения
186
Благодарностей
19
Баллы
18
Скорее всего дело в том, что нужно приводить тип данных к строковому типу.
Попробуйте вот так сделать:
Код:
project.Variables["region"].Value = Convert.ToString(read_fields["region"]);
Таки да! Спасибо!
 

Bullet

Client
Регистрация
16.12.2015
Сообщения
186
Благодарностей
19
Баллы
18
Можно еще чуть подробнее про UPDATE. Не могу до конца понять логику кода:

Код:
// данные для изменения в таблице
Dictionary<string, object> data = new Dictionary<string, object>() {
    {"value", "updated value for 1"}
};

// условие обновления
string condition = "id = '1'";

// делаем условие
if(!FastSqliteHelper.Update("example", data, condition)) {
    throw new Exception("Не получилось обновить данные в таблице!");
}
Как сюда укладывается запрос UPDATE example SET value = 1 WHERE id = '1' ?
Логически понятно, где таблица и условие FastSqliteHelper.Update("example", data, condition), а что такое data?
 

Lord_Alfred

Client
Регистрация
09.10.2015
Сообщения
3 916
Благодарностей
3 867
Баллы
113
Логически понятно, где таблица и условие FastSqliteHelper.Update("example", data, condition), а что такое data?
Это как раз соответствие "столбец" => "значение". То есть получается в примере выше в data: в поле value мы обновим данные и запишем туда "updated value for 1".

Как сюда укладывается запрос UPDATE example SET value = 1 WHERE id = '1' ?
А вот так будут выглядеть данные для этого запроса:
Код:
Dictionary<string, object> data = new Dictionary<string, object>() {
    {"value", 1} // второе значение - это object, нужно быть внимательным с ним :)
};
 

Bullet

Client
Регистрация
16.12.2015
Сообщения
186
Благодарностей
19
Баллы
18
Что то заплюхался я с многопотоком..
Логика:
Из таблицы берется первая запись, удовлетворяющая условию status = "Новый". Из этой записи читаются значения полей в переменные, в т.ч. id обрабатываемой записи и статус сразу меняется на "Запрос", чтобы параллельный поток не схватил ее же в обработку:

Код:
System.Data.SQLite.SQLiteDataReader read_fields = FastSqliteHelper.Select("query", "query_id", "status LIKE 'Новый' LIMIT 1");
    try {
        if (read_fields.Read()) {
            project.Variables["query_id"].Value = Convert.ToString(read_fields["query_id"]);
        }
    } finally {
            read_fields.Close();
    }
  
    // Обновим статус записи
    Dictionary<string, object> data = new Dictionary<string, object>() {
    {"status", "Запрос"}
    };
    // условие обновления
    string condition = "query_id = " + project.Variables["query_id"].Value;
  
    if(!FastSqliteHelper.Update("query", data, "query_id")) {
            throw new Exception("Не получилось обновить данные в таблице!");
    }
Далее идет выполнение логики работы шаблона и, если происходит исключение, вываливается в Bad End
В Bad End, кроме вывода информирующих сообщений, где именно косяк, есть кубик, который говорит - "Откати статус текущей записи назад, т.к. была ошибка, надо ее будет повторно обработать при следующем запуске":

Код:
    Dictionary<string, object> data = new Dictionary<string, object>() {
       {"status", "Новый"}
    };
    
    // условие обновлени
    string condition = "query_id = " + project.Variables["query_id"].Value;
    
    // делаем условие
    if(!FastSqliteHelper.Update("query", data, condition)) {
        FastSqliteHelper.DeInit();
       throw new Exception("Не получилось обновить данные в таблице!");
    }
В один поток - все ОК. Специально делаю ошибочные условия - статус записи откатывается. В многопоток - то да, то нет.. Закономерность словить не могу
 

Lord_Alfred

Client
Регистрация
09.10.2015
Сообщения
3 916
Благодарностей
3 867
Баллы
113
Что то заплюхался я с многопотоком..
Посмотри мой соседний топик по парсингу баз, там всё на этой либе и в многопотоке отлично работает )

удовлетворяющая условию status = "Новый"
А по факту юзаешь:
Код:
status LIKE 'Новый'
LIKE - это ж пипец какой тормоз в производительности.

Закономерность словить не могу
Скорее всего закономерность в том, что в первом коде что ты привел - не используются блокировки на уровне C#. Но могу ошибаться.
 
  • Спасибо
Реакции: Bullet

Bullet

Client
Регистрация
16.12.2015
Сообщения
186
Благодарностей
19
Баллы
18
LIKE - это ж пипец какой тормоз в производительности.
Скорее всего закономерность в том, что в первом коде что ты привел - не используются блокировки на уровне C#. Но могу ошибаться.
Спасибо за комментарии, земляк!
Нашел свой косяк в коде, теперь все забегало - запустил в тесты многопоток
 
  • Спасибо
Реакции: Lord_Alfred

Bullet

Client
Регистрация
16.12.2015
Сообщения
186
Благодарностей
19
Баллы
18
После какого то времени использования в многопотоке, обратил внимание, что растет файл db-wal (по размеру приблизился к основному).
Если я правильно понимаю, это некий журнал транзакций. Поискал инфу, как почистить - кроме VACUUM ничего не нашлось, эта команда тоже не помогла.
Или это нормальная ситуация и не нужно обращать внимание?
 

Lord_Alfred

Client
Регистрация
09.10.2015
Сообщения
3 916
Благодарностей
3 867
Баллы
113
На сколько помню - это нормально. Данные рано или поздно должны влиться в основную базу, но может быть это поможет:

появятся дополнительные файлы (*-shm и *-wal), при этом вам нужно будет решить: оставить фиксацию измененных данных в автоматическом режиме или вручную делать так называемый "checkpoint" (везде советуют делать его в отдельном процессе, когда работа с базой в этот момент сведена к минимуму).
 
  • Спасибо
Реакции: Bullet

Astraport

Client
Регистрация
01.05.2015
Сообщения
4 983
Благодарностей
4 433
Баллы
113
Решал снова попробовать этот враппер в одном проекте и столкнулся с двумя проблемами:
1. Например у меня есть таблица где десяток полей и мне нужно удалить дубли рядов по нескольким полям. Например там товары, артикулы у всех разные, а вот названия, цены и бренды одинаковые и мне нужно удалить дубли по названию, цене и бренду одновременно. Как это можно сделать?
Есть примерно такие конструкции, но как их с враппером организовать - непонятно.
DELETE FROM sms WHERE rowid NOT IN (SELECT min(rowid) FROM sms GROUP BY address, body);
2. Как получить количество рядов в результате FastSqliteHelper.Select? Метода нет такого. Да, можно запросом Count(*) получить искомую цифру, но может быть и в результате запроса SELECT она где-то есть?)
 
Последнее редактирование:

Lord_Alfred

Client
Регистрация
09.10.2015
Сообщения
3 916
Благодарностей
3 867
Баллы
113
1. Например у меня есть таблица где десяток полей и мне нужно удалить дубли рядов по нескольким полям. Например там товары, артикулы у всех разные, а вот названия, цены и бренды одинаковые и мне нужно удалить дубли по названию, цене и бренду одновременно. Как это можно сделать?
Такую сложную логику не организовывают в духе "сделаем один большой и тяжелый запрос к БД, пусть она там внутри сама шуршит".
Нужно на уровне C# решать это, чтоб было более кастомизируемо под твои правила и условия.
Дергаешь все уникальные артикулы, а потом по каждому проходишься и ищешь дубли (или сразу удаляешь, если принцип оставления нужной записи - простой, в духе "самая старая запись").

2. Как получить количество рядов в результате FastSqliteHelper.Select? Метода нет такого. Да, можно запросом Count(*) получить искомую цифру, но может быть и в результате запроса SELECT она где-то есть?)
Ты не путаешь ряды и строки?
Количество рядов нужно доставать из какой-то системной информации о базе (я хз как).
А количество строк - через функцию count() внутри SQL, да.

Ну и присмотрись к методам, которые позволяют выполнять произвольные SQL-запросы:
  • FastSqliteHelper.Query — Любой запрос к БД, возвращающий количество затронутых строк.
  • FastSqliteHelper.QueryReader — Любой запрос к БД, возвращающий объект SQLiteDataReader с содержимым.
  • FastSqliteHelper.QueryScalar — Любой запрос к БД, возвращающий первый столбец первой строки.
 
  • Спасибо
Реакции: Astraport

Astraport

Client
Регистрация
01.05.2015
Сообщения
4 983
Благодарностей
4 433
Баллы
113
Ты не путаешь ряды и строки?
Ряд (row) и строка это одно и тоже в контексте терминов БД. Тоже самое что и запись (record), тоже часто используется. А вот поле (field) или колонка (column) это наверное то, что имеешь в виду под "рядом"?
А количество строк - через функцию count() внутри SQL, да.
Вот в твоём шаблоне данные собираются так:
C#:
foreach(dynamic row in result) {
        project.SendInfoToLog("Строка: " + row["id"] + " -> " + row["value"]);
    }
Как мне до этого цикла получить из result количество рядов или записей?
 

Lord_Alfred

Client
Регистрация
09.10.2015
Сообщения
3 916
Благодарностей
3 867
Баллы
113
А вот поле (field) или колонка (column) это наверное то, что имеешь в виду под "рядом"?
Да, что-то я как раз напутал в терминологии получается )

Вот в твоём шаблоне данные собираются так:
Хороший вопрос. Или предварительно сделать запрос с count() при этих же условиях where или копать внутрь реализации sqlite, смотреть что он там в ридере возвращает и можно ли через это получить количество строк (подозреваю, что можно, но я так не делал и у меня не было надобности в таком).
 

Porosenok

Client
Регистрация
26.09.2010
Сообщения
1 280
Благодарностей
96
Баллы
48
А никто не знает есть ли в sqlite аналог такого запроса
"INSERT INTO table(page) SELECT '"+str+"' FROM DUAL WHERE NOT EXISTS (SELECT page FROM table WHERE page = '"+str+"')"
то есть это он проверяет есть ли такая ячейка, если нет то делает строку с ней
 

Кто просматривает тему: (Всего: 3, Пользователи: 0, Гости: 3)