Estoy portando una aplicación CRUD de tamaño mediano de .Net a Qt y estoy buscando un patrón para crear clases de persistencia. En .Net lo general creado clase persistencia abstracto con métodos básicos (insertar, actualizar, eliminar, seleccionar) por ejemplo:Clases de persistencia en Qt
public class DAOBase<T>
{
public T GetByPrimaryKey(object primaryKey) {...}
public void DeleteByPrimaryKey(object primaryKey) {...}
public List<T> GetByField(string fieldName, object value) {...}
public void Insert(T dto) {...}
public void Update(T dto) {...}
}
Entonces, con subclases para mesas/dtos específicos y añadió atributos de diseño de tabla DB:
[DBTable("note", "note_id", NpgsqlTypes.NpgsqlDbType.Integer)]
[DbField("note_id", NpgsqlTypes.NpgsqlDbType.Integer, "NoteId")]
[DbField("client_id", NpgsqlTypes.NpgsqlDbType.Integer, "ClientId")]
[DbField("title", NpgsqlTypes.NpgsqlDbType.Text, "Title", "")]
[DbField("body", NpgsqlTypes.NpgsqlDbType.Text, "Body", "")]
[DbField("date_added", NpgsqlTypes.NpgsqlDbType.Date, "DateAdded")]
class NoteDAO : DAOBase<NoteDTO>
{
}
Gracias al sistema de reflexión .Net pude lograr la reutilización de códigos pesados y la creación sencilla de nuevos ORM.
La forma más simple de hacer este tipo de cosas en Qt parece ser utilizar clases de modelo del módulo QtSql. Lamentablemente, en mi caso proporcionan una interfaz demasiado abstracta. Necesito al menos soporte de transacciones y control sobre confirmaciones individuales que QSqlTableModel no proporciona.
¿Podría darme algunos consejos para resolver este problema usando Qt o dirigirme a algunos materiales de referencia?
Actualización:
Sobre la base de las pistas de Harald he implementado una solución que es bastante similar a las clases de .NET anteriores. Ahora tengo dos clases.
UniversalDAO que hereda QObject y se ocupa de QObject dtos utilizando el sistema metatipo:
class UniversalDAO : public QObject
{
Q_OBJECT
public:
UniversalDAO(QSqlDatabase dataBase, QObject *parent = 0);
virtual ~UniversalDAO();
void insert(const QObject &dto);
void update(const QObject &dto);
void remove(const QObject &dto);
void getByPrimaryKey(QObject &dto, const QVariant &key);
};
Y un genérico SpecializedDAO que arroja datos obtenidos de UniversalDAO al tipo apropiado:
template<class DTO>
class SpecializedDAO
{
public:
SpecializedDAO(UniversalDAO *universalDao)
virtual ~SpecializedDAO() {}
DTO defaultDto() const { return DTO; }
void insert(DTO dto) { dao->insert(dto); }
void update(DTO dto) { dao->update(dto); }
void remove(DTO dto) { dao->remove(dto); }
DTO getByPrimaryKey(const QVariant &key);
};
Utilizando lo anterior, declarar la clase DAO concreta de la siguiente manera:
class ClientDAO : public QObject, public SpecializedDAO<ClientDTO>
{
Q_OBJECT
public:
ClientDAO(UniversalDAO *dao, QObject *parent = 0) :
QObject(parent), SpecializedDAO<ClientDTO>(dao)
{}
};
Desde dentro ClientDAO tengo que fijar un poco de información de base de datos para UniversalDAO. Ahí es donde mi aplicación pone feo porque lo hago de esta manera:
QMap<QString, QString> fieldMapper;
fieldMapper["client_id"] = "clientId";
fieldMapper["name"] = "firstName";
/* ...all column <-> field pairs in here... */
dao->setFieldMapper(fieldMapper);
dao->setTable("client");
dao->setPrimaryKey("client_id");
lo hago en el constructor lo que no es visible a simple vista de un usuario que navega a través de la cabecera. En la versión de .Net fue fácil de detectar y entender.
¿Tiene alguna idea de cómo podría mejorarla?
Usando sus consejos y enlaces como punto de partida, he publicado mi implementación de la solución. Dime por favor lo que piensas al respecto. – zarzych