Добро пожаловать в серию статей о разработке Advanced EOS, здесь я коснусь передовых методов и функций, которые редко рассматриваются в учебных пособиях или курсах. Цель этой серии - собрать воедино недостающие части, которые вам понадобятся для завершения своих навыков в качестве разработчика распределенных приложений в сети EOS. Каждый пост отсортирован по сложности, поэтому, если вы хотите получить общий обзор, я бы рекомендовал начать с части 1 и постепенно продвигаться вверх. Полный код этих примеров можно найти здесь, на GitHub.



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

Этот пост будет относительно коротким, но он будет посвящен фундаментальному и, казалось бы, не раскрытому методу с многоиндексными таблицами и уникальными первичными индексами.

Отношения один-ко-многим

Я вырос в Базах данных графиков, прекрасной стране, где данные напрямую связаны, также как и связанные данные, например, MySQL объединяется, но на стероидах. Итак, если вы похожи на меня и освоили основы таблиц EOS, то, вероятно, задали вопрос, но как мне создать связь между несколькими таблицами и строками? В этом примере мы рассмотрим не один, а несколько методов создания отношений один ко многим с использованием таблиц EOS multi_index.

В этих примерах мы рассмотрим три различных метода создания отношений между строками нашей таблицы EOS; Векторы, индексы и области видимости.

Для простоты следующие примеры не будут проверять учетную запись вызывающего абонента.

Векторы

Векторы в C ++ представляют собой структуру динамического массива, позволяющую хранить коллекцию значений произвольного размера. Давайте изучим векторы, сохранив массив уникальных Item идентификаторов для игроков Profile. Здесь идентификаторы, хранящиеся в таблице Profile, будут соответствовать id строкам в нашей таблице Item.

struct Profile {
  name              account;
  vector<uint64_t>  items;
}

struct Item {
  uint64_t          id;
  string	            name;
}

Теперь внутри нашего create() метода мы можем просто вставить ссылку на Item, используя Item.id, который мы сохранили ранее, и метод push_back() векторного класса.

owners.modify(currentPlayer, 0, [&](auto& owner) {
  owner.items.push_back(itemID);
});

Мы можем даже пойти дальше и определить вложенную коллекцию в нашем векторе элементов vector<uint64_t, uint32_t> items;, где uint64_t - наш идентификатор элемента, а uint32_t - возраст элемента. В качестве альтернативы вторичная структура (коллекция) может храниться внутри вектора, например vector<Item> items;.

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

Вторичные индексы

Использование вторичных индексов предоставляет альтернативный способ масштабирования наших ссылок, избегая при этом чрезмерно больших массивов (векторов) в строках нашей таблицы. Это также позволяет нам плавно перемещаться между родительскими и дочерними строками в нашей таблице, используя обратную ссылку от дочернего элемента к родительскому. Давайте посмотрим на это на практике, расширив наш проект из предыдущего раздела «Вектор». Сначала мы собираемся изменить наши структуры, удалив вектор items из структуры Profile и добавив ссылку на нашего владельца в структуру Item.

struct Profile {
    name 	account;
}

struct Item {
    uint64_t	uid;
    string	name;
    name		owner;
}

Теперь нам нужно добавить функцию поиска, чтобы мы могли найти наш Item по owner. Давайте создадим вторичный индекс и добавим его в наше multi_index определение.

struct Item {
    ...
    uint64_t get_owner() const { return owner; }
}

typedef multi_index<N(items), Item, indexed_by<N(byowner), const_mem_fun<Item, uint64_t, &Item::get_owner>> item_table;

Где имя нашего индекса будет byowner, и он вернет ключ uint64_t owner с помощью функции get_owner.

Метод multi_index позволяет нам определять до 16 дополнительных индексов для каждой таблицы.

Мы больше не будем обновлять Player из нашей функции добавления элемента. Вместо этого мы будем хранить ссылку на подписавшего account в свойстве owner нашего вновь созданного элемента. Давайте обновим наше additem действие, чтобы добиться этого;

items.emplace(account, [&](auto& item) {
    ...
    item.owner = account;
});

Теперь мы можем получить все наши предметы для такого игрока;

void indexes::get(const name account) {
  item_table items(_self, _self);
  auto accounts_items = items.get_index<N(byowner)>();
  auto iter = accounts_items.lower_bound(account);
  while (iter != accounts_items.end(); iter++) { // Do stuff }
}

Сфера

В этом последнем методе мы будем использовать область видимости таблицы для представления отношений между строками нашей таблицы. Использование области действия добавляет форму защищенной безопасности, при которой данные могут быть найдены только в том случае, если область действия известна.

Структура данных EOS

- code       * The account name assigned write permission (contract)
-- scope     * The account where the data is stored
--- table    * Name of the table being stored
---- record  * A table row

Мы собираемся изменить наш Item, чтобы использовать область видимости таблицы для поиска всех предметов игрока. Чтобы упростить наш проект, мы удалим owner ссылку и get_owner() индекс из определения нашей Item структуры и таблицы multi_index.

struct Item {
    uint64_t    	id;
    string	name;
    auto primary_key() const { return id; };
    EOSLIB_SERIALIZE(Item, (id)(name));
};

typedef multi_index<N(items), Item> item_table;

Настоящая магия происходит с нашими сеттерами и геттерами, давайте посмотрим, как они изменились.

item_table items(_self, account);
auto item = items.emplace(account, [&](auto& item) {
  ... configure item
});

Если вы посмотрите на создание экземпляров нашей таблицы, то заметите, что теперь мы используем объем account, а не code самого контракта. Наше действие get было изменено таким же образом.

item_table items(_self, account);
auto iter = playerItems.lower_bound();
while (iter != playerItems.end()) { ... }

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

Получайте лучшие предложения по программному обеспечению прямо в свой почтовый ящик