tags:

views:

168

answers:

4

I am writing a project where I need to implement a stripped down version of an ORM solution in C++. I am struck in implementing 1-n relationships for the same.

For instance, if the following are the classes:

class A
{
    ...
}

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

I have provided load/save methods for loading/saving to the db. Now, if I take the case of B and the following workflow:

  • 1 entry from _a_list is removed
  • 1 entry from _a_list is modified
  • 1 entry is added to _a_list

Now, I need to update the db using something like "b.save()". So, what would be the best way to save the changes, i.e, identify the additions, deletions and updates to _a_list.

+1  A: 

One strategy would be to use an enum to represent the 'status' of a record. Ie

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

You would give each record a RecordState (defaulting to RECORD _NEW / RECORD _UNMODIFIED as appropriate) and when Save() was called, it would perform the appropriate action for every record and reset their state to RECORD _UNMODIFIED. Deletes would be eliminated from the list as they were processed.

Adam Luchjenbroers
+1  A: 

My first idea would be to encapsulate all possible db operations as command objects (Command pattern). This way you can create as many commands as you want until you call the Save() method to update the database. Here you need to ensure that these commands are handled as transactions. A quick implementation would be something like this:

Header:

#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 */ }
};

Source:

#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)
{
}

Test Main:

#include "DbClass.h"

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

    return 0;
}
Holger Kretzschmar
A: 

Record status is indeed a good idea.

I suggest that either:

(a) the app keeps deleted objects in the arrays and they are actually removed only when the ORM-like code is called to do a save (which is when it does INSERTs, UPDATEs and DELETEs)

OR

(b) the ORM context needs to maintain internally a behind-the-scenes list of all objects that have either been SELECTEDed from disk or created in RAM for each database transaction (or if not using transactions, connection). This list is iterated when the ORM is asked to save and INSERTs, UPDATEs and DELETEs are based on this list.

In the second case, you often find an additional requirement to be able to dissociate/detach an object from the ORM in some parts of the system, to create a persistent snapshot of a state or a modified version of an object that (according to some high level data flow model or other) is not immediately for storage, so an extra bit or enum state is desirable to reflect detachment. You may wish to be able to reassociate/reattach an object with an ORM transaction but note that there may be integrity issues involved here that if they need handling must be handled, and the method for handling them is often application specific.

Note that freshly created objects that are deleted before their first save should not generate a SQL DELETE hence an enum with UNMODIFED, NEW, CHANGED, DELETED is in practice often not enough, you also need NEW_DELETED and if going along with my theories DETACHED.

martinr
+1  A: 

A bit late but in my opinion what you'll need / needed is a Unit Of Work. You current design is like a Registry which plays nicely with the UoW.

Derick