Реализация отображения 1 к n для ORM c ++ - PullRequest
5 голосов
/ 16 декабря 2009

Я пишу проект, в котором мне нужно реализовать урезанную версию решения ORM на C ++. Я поражен в реализации 1-n отношений для одного и того же.

Например, если следующие классы:

class A
{
    ...
}

class B
{
    ...
    std::list<A> _a_list;
    ...
}

Я предоставил методы загрузки / сохранения для загрузки / сохранения в БД. Теперь, если я возьму случай B и следующий рабочий процесс:

  • 1 запись из _a_list удалена
  • Изменена 1 запись из _a_list
  • 1 запись добавлена ​​в _a_list

Теперь мне нужно обновить базу данных, используя что-то вроде "b.save ()". Итак, что будет лучшим способом сохранить изменения, то есть определить добавления, удаления и обновления в _a_list.

Ответы [ 5 ]

3 голосов
/ 16 декабря 2009

Моя первая идея заключалась бы в том, чтобы инкапсулировать все возможные операции БД в качестве объектов команды (шаблон команды). Таким образом, вы можете создавать столько команд, сколько захотите, пока не вызовете метод Save () для обновления базы данных. Здесь необходимо убедиться, что эти команды обрабатываются как транзакции. Быстрая реализация будет выглядеть примерно так:

Заголовок:

#include <vector>

using namespace std;

class B;
class Cmd;

class B
{
    private:
        vector<Cmd*> m_commands;
    public:
        void AddCmd( Cmd* p_command );
        void Save();
};

class Cmd
{
    protected:
        B* m_database;

    public:
        Cmd( B* p_database );
        virtual void Execute() = 0;
        virtual void Undo() = 0;
};

class InsertCmd : public Cmd
{
    private:
        int m_newEntry;
    public:
        InsertCmd( B* p_database, int p_newEntry );
        void Execute() { cout << "insert " << m_newEntry << endl; }
        void Undo()    { /* undo of insert */ }
};

* Источник: 1009 *

#include "DbClass.h"

void B::AddCmd( Cmd* p_command )
{
    m_commands.push_back(p_command);
}

void B::Save()
{
    for( unsigned int i=0; i<m_commands.size(); i++ )
        m_commands[i]->Execute();
}

Cmd::Cmd( B* p_database ) : m_database(p_database)
{
    m_database->AddCmd(this);
}

InsertCmd::InsertCmd( B* p_database, int p_newEntry ) 
: Cmd(p_database), m_newEntry(p_newEntry)
{
}

Основное испытание:

#include "DbClass.h"

int main()
{
    B database;
    InsertCmd  insert( &database, 10 );
    database.Save();

    return 0;
}
1 голос
/ 20 апреля 2012

Я хочу поделиться своим мнением о том, как можно реализовать отношение «1 к n». Сторона «1» является главной таблицей, а сторона «n» соответствует ведомой (дочерней) таблице. Полагаю, мы хотим манипулировать отношениями с обеих сторон. С точки зрения ведомого, отношение будет выглядеть как одно свойство объекта, возможно, с возможностью устанавливать / изменять / очищать ссылку на объект, указанную свойством. С точки зрения мастера такое же отношение будет подобным коллекции свойством, предоставляя нам средства для перебора / добавления / удаления ссылок на объекты из этой коллекции. Поскольку любые изменения, внесенные в одну сторону отношения, должны быть немедленно сделаны доступными для другой стороны, у нас есть два варианта:

  1. Незамедлительно распространить изменения на всех участников отношения. В этом случае упомянутое выше коллекционное свойство может быть реализовано с использованием класса контейнера общего назначения с переопределением методов изменения.
  2. Введите некоторый промежуточный объект «экземпляр отношения», который будет владеть всей информацией об отношении только для одного экземпляра главного объекта. В этом случае каждый вызов свойств с любой стороны будет извлекать запрошенную информацию из этого промежуточного объекта.

Выбор между двумя включает ответ на несколько важных вопросов:

  1. Как должны создаваться экземпляры отображаемых классов? Можем ли мы создать экземпляр, не сохраняя его в IdentityMap? Как насчет создания связанной структуры вновь созданных объектов?
  2. Можно ли копировать экземпляры отображенных классов, сохраняя при этом некоторые знания друг о друге, для распространения изменений? Или, может быть, у нас должен быть ровно один экземпляр для каждой записи таблицы?
  3. Кто отвечает за удаление объектов во всех возможных случаях?

В любом случае есть некоторые функции, которые обычно присутствуют в любом ORM-подобном решении. Например, шаблон проектирования IdentityMap предполагает, что вы регистрируете все экземпляры сопоставленных классов, которые должны отображать свои изменения в БД в каком-либо реестре. Это необходимо для последующего выполнения операции «промывки». Конечно, это требует поддержания статуса записи. Я обнаружил, что подход «экземпляр отношения» относительно легче реализовать. Вы можете найти реализацию в моем общем ORM-решении общего назначения для C ++: YB.ORM . В частности, обратите внимание на исходные файлы DataObject.h, DataObject.cpp и тесты в TestDataObject.cpp (папка lib / orm /). Библиотека YB.ORM использует объекты с типизированным типом внутри с помощью статически типизированных «тонких» оболочек, в отличие от вашего примера кода. Класс DataObject представляет экземпляр сопоставленного класса, где правила отображения приведены в описании метаданных. Такие объекты всегда размещаются в куче и не подлежат копированию. Они хранят значения данных. У них есть ссылка на информацию метаданных для сопоставленной таблицы. Конечно, текущее состояние поддерживается (один из: New, Ghost, Dirty, Sync, ToBeDeleted, Deleted) внутри этих объектов. Для поддержки отношений, в которых этот класс представляет сторону «n», каждый из них имеет набор указателей на экземпляры класса RelationObject (slave_relations_ member). Для поддержки отношений, в которых этот класс представляет сторону «1», каждый из них также имеет набор общих указателей на экземпляры класса RelationObject (master_relations_ member).

Класс RelationObject представляет собой экземпляр отношения. Такие объекты всегда размещаются в куче и не подлежат копированию. Они хранят и перечисляют указатели на связанные экземпляры DataObject: один указатель на master и набор общих указателей на slave. Таким образом, они «владеют» подчиненными DataObject экземплярами, а DataObject экземпляры «владеют» (косвенно) всеми подчиненными объектами. Обратите внимание, что RelationObject сам поддерживает что-то вроде состояния для поддержки отложенной загрузки.

1 голос
/ 30 марта 2010

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

1 голос
/ 16 декабря 2009

Статус записи действительно хорошая идея.

Я предлагаю либо:

(a) приложение хранит удаленные объекты в массивах, и они фактически удаляются только тогда, когда ORM-подобный код вызывается для сохранения (то есть, когда он выполняет INSERTs, UPDATEs и DELETEs)

OR

(b) контекст ORM должен поддерживать внутренний закулисный список всех объектов, которые либо были ВЫБРАНЫ с диска, либо созданы в ОЗУ для каждой транзакции базы данных (или, если транзакции не используются, соединение). Этот список повторяется, когда ORM требуется сохранить, а INSERT, UPDATE и DELETE основаны на этом списке.

Во втором случае вы часто обнаруживаете дополнительное требование, чтобы иметь возможность отделить / отсоединить объект от ORM в некоторых частях системы, чтобы создать постоянный снимок состояния или измененную версию объекта, который (в соответствии с какой-либо моделью потока данных высокого уровня или другой) не предназначен для немедленного хранения, поэтому желательно, чтобы дополнительный бит или состояние перечисления отражали отсоединение. Возможно, вы захотите иметь возможность повторно связать / повторно присоединить объект с транзакцией ORM, но обратите внимание, что здесь могут быть проблемы с целостностью, которые, если они нуждаются в обработке, должны быть обработаны, а метод обработки их часто зависит от приложения.

Обратите внимание, что недавно созданные объекты, которые удаляются перед их первым сохранением, не должны генерировать SQL DELETE, поэтому на практике часто бывает недостаточно перечисления с UNMODIFED, NEW, CHANGED, DELETED, вам также нужен NEW_DELETED, и если вы придерживаетесь моих теорий ОТДЕЛИЛИ.

1 голос
/ 16 декабря 2009

Одной из стратегий было бы использование enum для представления «статуса» записи. Т.е.

enum RecordState {
    RECORD_UNMODIFIED,
    RECORD_NEW,
    RECORD_CHANGED,
    RECORD_DELETED
};

Вы должны присвоить каждой записи RecordState (по умолчанию RECORD _NEW / RECORD _UNMODIFIED в зависимости от ситуации), и при вызове Save () она выполнит соответствующее действие для каждой записи и сбросит их состояние до RECORD _UNMODIFIED. Удаления будут удалены из списка по мере их обработки.

...