В Simple UI оффлайн хранение можно организовать в NoSQL или в SQLite. И несмотря на очевидные плюсы NoSQL, старый добрый SQL рано списывать. Быстрые агрегатные функции, быстрый поиск по сложным фильтрам – все это к SQL. Но есть проблемы – для этого нужно знать собственно сам язык SQL (более того, нужно знать конкретную реализацию под конкретную СУБД, а они существенно отличаются) и конструкции для рутинных CRUD – операций в коде довольно громоздкие, особенно если решение большое и таблиц много – нужно все прописывать вручную. Для того придумали ORM (object-relational mapper) – надстройки над SQL которые позволяют:
• Работать с таблицами СУБД как с объектами – обращаться к атрибутам объекта, наследовать и т.д.
• Соответственно не нужно знать синтаксиса – ты работаешь с объектами
• Не задумываться, с какой СУБД работаешь – NoSQL, PostgreSQL, MySQL – ORM работает со всеми, а разработчик работает с единым интерфейсом ORM. Соответственно опять же не нужно знать особенности и читать доки
• Писать запросы как попало – а ORM сама сделает оптимизацию
• За счет простоты повышается читаемость кода и проще поддерживать проекты
В общем во всех отношениях чудесная вещь, призванная упростить жизнь разработчика как на маленьких, так и на больших проектах. Скажем так, после перехода на ORM назад к сырому SQL уже не захочется, по крайней мере в CRUD–операциях точно. С SELECT-ами еще можно поспорить. Но дело в том, что ничего не мешает параллельно ORM работать с таблицами напрямую. Хоть средствами самого ORM, хоть другими способами. Т.е. ORM это просто обертка, которая преобразует код работы с объектами в набор SQL-запросов, которые делают транзакции и ничего не мешает обращаться к таблицам другими способами. Т.е., пожалуй что, не найти ни одного аргумента против ее использования.
На самом деле технология давно пустила корни в различных стеках – она есть в Django (собственный ORM), есть SQLAlchemy который используется в множестве именитых проектах и т.д. Для стека SimpleUI+Python+Android я выбрал Pony ORM https://docs.ponyorm.org/firststeps.html . Она ничем не хуже SQL Alchemy или Peewee, но очень простая (что и нужно для Simple) и там есть, например, такие встроенные штуки как работа с типами JSON – т.е. в таблице хранится JSON, а Pony обращается к объекту таблицы и к JSON объекту как к JSON-объекту. Т.е., грубо говоря, можно обращаться через точку.
Работа через ORM напоминает работу в 1С через менеджер объекта (справочника, документа или регистра) с той лишь разницей, что в 1С нет ООП, нет классов и такие вещи, как, например, табличная часть, подчиненная строке табличной части, изобразить напрямую нельзя.
Вся документация по Pony с примерами есть в вышеприведенной ссылке, от себя я добавил примеры работы с базой через ORM в конфигурации «Примеры с ORM» - там добавление, редактирование, удаление, вложенные объекты и выборки. А также пример, как можно обращаться к «Документам» через ORM, раз уж там с поддержкой JSON в типах такое можно.
Комплект разработчика для версии 7.75 можно скачать в //infostart.ru/public/1153616/
Вкратце изложу на примерах, как использовать Pony в Simple UI. Тут везде используется только код Python – т.е. только оффлайновое выполение кода. Из 1С онлайн такое не сделаешь
1. Описываем объекты в общем модуле (в Pony они называются «сущностями»). Тут все просто: Required означает обязательный реквизит, Optional – необязательный, PrimaryKey – первичный ключ (по умолчанию создается id, это не нужно декларировать) и Set – это набор (аналог табличной части) который можно включать в себя другие объекты – те объекты какие то другие и т.д.
Типы значений могут быть простые и могут быть другие объекты. Подробнее тут https://docs.ponyorm.org/entities.html
Например, я объявил 2 объекта «Люди» и «Машины». У каждого человека могут быть несколько машин.
from pony.orm import Database,Required,Set,Json,PrimaryKey,Optional
import datetime
class Person(db.Entity):
name = Required(str)
age = Required(int)
cars = Set('Car')
class Car(db.Entity):
make = Required(str)
model = Required(str)
owner = Required(Person)
2. Но эти объекты еще не созданы, потому что это было просто описание – в СУБД ничего не произошло. Да и в какой СУБД? Pony о ней ничего не известно. Поэтому тут же, в объем модуле я пишу что СУБД будет SQLite и указываю путь к ней.
db = Database()
db.bind(provider='sqlite',
filename='//data/data/ru.travelfood.simple_ui/databases/SimpleWMS', create_db=True)
А также объявляю функцию init с вызовом generate_mapping которую надо поставить в ПриЗапуске чтобы каждый раз когда программа запускалась она выполнялась она собсвенно и создает таблицы. После запуска приложения можно проверить и убедиться в консоли что наши таблицы созданы
def init():
db.generate_mapping(create_tables=True)
3. Ну и далее работаем уже с объектами Person и Car
Добавить объект (тут и далее они импортируются из общего модуля ui_global)
with db_session:
p = ui_global.Person(name=hashMap.get('name'), age=int(hashMap.get('age')))
Получить объект можно по id:
with db_session:
p = ui_global.Person[int(hashMap.get("id"))]
p.name =hashMap.get('name')
p.age =int(hashMap.get("age"))
Добавить машину к владельцу
with db_session:
ui_global.Car(make=hashMap.get('brand'), model=hashMap.get('model'), owner=ui_global.Person[int(hashMap.get("id"))])
commit()
Удалить объект (предварительно проверив что он есть)
with db_session:
if ui_global.Person.exists(id=int(hashMap.get("id"))):
ui_global.Person[int(hashMap.get("id"))].delete()
Простой запрос с обходом полей
query = select(c for c in ui_global.Car if c.owner==ui_global.Person[int(hashMap.get("id"))])
rows=[]
for car in query:
rows.append({"make":car.make,"model":car.model,"id":car.id})