0

I want to display lists of business layer objects in 'grid' controls. I also want to edit any given object in a dialog box.

The objects are persisted in a relational database that also performs referential and multi-user integrity. Although the business objects are quite similar to the database tables, I would like to decouple them to a degree using some sort of 'data mapper'.

My objects are derived from a myDataObject base class. The base class has a static member as follows:

// Initialise a static list of member names for this class. Used when the object is displayed in a grid.
const std::vector<myDataObjectDescriptor> myDataObject::m_Descriptors 
{
    { myDO_INT_FIELD, "UID", "m_UID", 40, false, false },
    { myDO_STRING_FIELD, "TITLE", "m_Title", 200, true, false },
    { myDO_STRING_FIELD, "DESCRIPTION", "m_Description", 400, true, false }
};

This descriptor list allows the grid control to directly render the object list with the correct column titles and widths. I can extend the base class descriptors using a lambda:

// Static initialisation of descriptor list using a lamda.
const std::vector<myDataObjectDescriptor> myDerivedDataObject::m_Descriptors = [] 
{ 
    std::vector<myDataObjectDescriptor> v = myDataObject::m_Descriptors; 
    v.push_back ({myDO_STRING_FIELD, "BACKGROUND", "m_Background", 120, false, false}); 
    v.push_back ({myDO_STRING_FIELD, "FONT", "m_Font", 120, false, false}); 
    return v; 
} ();

So far so good. Now, I would like to create a list of objects from a database query, the list can be a std::vector<some class derived from myDataObject>. My database layer returns a result set which allows rows to be retrieved one at a time.

How can I write a data mapper that takes a reference to the object list (std:vector<some class derived from myDataObject>&) and a reference to the result set and populates the list?

Additional info:

Currently I have 2 methods that are overridden in each class derived from myDataObject:

  • FromData (myResultSet& resultset): populates 'this' object from the current row in the result set.
  • SetByName (string name, variant value): sets an attribute based on its stringified name. Used by FromData to set an attribute based on the field name in the result set.

There are multiple things that I don't like about this, but mostly:

  • myDataObject shouldn't know anything about the database layer (too closely coupled).
  • SetByName is a series of if statements that sets an attribute if the 'name' parameter is matched, i.e.

    if (name == "m_Title")
        m_Title = value;
    

Edit re. comment by rumburak: Once I have solved the problem of reading, it is my plan to somehow do the reverse for persisting data.

iwbnwif
  • 327
  • 2
  • 13

2 Answers2

0

Since you are asking about reading data from the database, and seem to have no questions about writing: Can't you just do the inverse of whatever you do for persisting the data in the database?

If not:

You could translate the result rows into intermediate objects which decouple myDataObject and resultRow. This object could have known data members for well-known fields and one or more maps for other fields, e.g.

struct mySerializedObject
{
   int id;
   std::string title;
   std::map<std::string, std::string> texts;
   std::map<std::string, int> numbers;
};

And then the fromSerialized functions of the data objects can just pick what they need.

Rumburak
  • 3,416
  • 16
  • 27
  • Thanks! You are right, I should have mentioned about writing, my plan was to solve reading first and then do the opposite for writing! Your answer and the discussion in [this question](http://stackoverflow.com/questions/8844105/repository-and-data-mapper-pattern) have put me on a good way. I will put my final solution as an answer if I can. – iwbnwif Mar 18 '16 at 21:18
0

Thanks to the suggestion by @rumburak and this answer I have solved my problem for now.

I translate a result row into a general-purpose intermediate object which is defined as follows:

typedef std::unordered_map<std::string, boost::any> myDataRow;

Then in my database layer I have a GetRow() method that returns a reference to a myDataRow. The method iterates over the columns of the resultset's current record and populates the row object.

myDataRow& myDataResultSet::GetRow()
{
    // Get the column names.
    std::vector<myDataColumn>& cols = GetColumns();

    // Iterate through the columns, setting the mapped value against the column name.
    // Note: the map's columns are automatically be created on the first use of this function and their contents updated thereafter.
    int i = 0; for (myDataColumn col : cols)
    {
        switch(col.m_Type)
        {
            case dtInteger:
                m_Data[col.m_Name] = GetInt(i++);
                break;

            case dtString: 
                m_Data[col.m_Name] = GetString(i++);
                break;
        }

    return m_Data;
}

Now 'data aware' objects can be initialised from the intermediate myDataRow class.

void myDataObject::FromData(const myDataRow& row)
{
    auto it = row.find("UID");
    m_UID = it != row.end() ? boost::any_cast<int>(it->second) : 0;

    it = row.find("TITLE");
    m_Title = it != row.end() ? boost::any_cast<std::string>(it->second) : "";

    it = row.find("DESCRIPTION");
    m_Description = it != row.end() ? boost::any_cast<std::string>(it->second) : ""; : ""; 
}

With each derived class calling its parent's FromData(). They also have a convenience constructor myDataObject(const myDataRow&).

The data mapper layer now consists of querying the database and populating objects from result set rows, i.e.:

myDerivedDataObject temp(results->GetRow());

The 'data mapping' part consists of making sure that the resultset columns have names that correctly map to the data object's members.

Community
  • 1
  • 1
iwbnwif
  • 327
  • 2
  • 13
  • I am going to mark this as my answer because it is what I have ended up doing, however I hope that doesn't detract from the help given by @Rumburak. – iwbnwif Mar 18 '16 at 22:04