2009-09-23 20 views
8

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?

Respuesta

5

Por lo que yo sé, no hay nada preparado que dé a esta instalación directamente en qt. Hay algunos enfoques posibles.

  • Implementar los campos como Q_PROPERTY, el se reflejan a continuación, a través del sistema MetaClass y se puede utilizar para implementar la funcionalidad DAO genérico

  • Aún se podía utilizar el QSqlTableModel pero encapsular escribe con las transacciones, si una transacción falla, actualice el modelo desde el DB. La viabilidad depende del tamaño de los datos que tenga en el modelo.

Actualmente usamos un enfoque basado TableModel/QSqlRecord para la lectura y la escritura, no hay ninguna asignación ORM hecho en nuestro sistema. He estado tratando de diseñar un enfoque más genérico, pero el trabajo de refactorización que tendríamos que hacer para llegar es costoso en este momento.

http://giorgiosironi.blogspot.com/2009/08/10-orm-patterns-components-of-object.html Este enlace no tiene que ver con Qt, pero una buena visión de conjunto de patrones de implementación recientemente

+0

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

2

Tegesoft ha lanzar una nueva versión de su biblioteca llamada CAMP que proporcionará C++ en tiempo de ejecución como la reflexión que está utilizando en .Net. Creo que esto le permitirá lograr su aplicación como lo ha hecho en .Net.

+0

Parece muy prometedor, pero no quiero aumentar el impulso como dependencia si puedo resolver el problema en Qt puro. – zarzych

2

También hay una nueva biblioteca ORM C++ de código abierto: QxOrm. QxOrm se basa en QtSql Qt módulo para comunicarse con la base de datos y boost :: serialization para serializar sus datos con formato XML y binario. El sitio web está en francés, pero el código de muestra rápido y el código del tutorial están en inglés (una traducción está en progreso ...).

3

Si desea un ORM que solo dependa de Qt y que se basa en el sistema de metaobjetos de Qt para proporcionar una introspección, puede intentar probar QDjango. Además de las operaciones básicas de creación/actualización/eliminación a nivel de modelo, proporciona una clase de plantilla de conjunto de consulta (modelada según los conjuntos de consultas de django) que permite crear búsquedas bastante complejas. La integración de QtScript también está en marcha.

1

... Y una nueva Qt ORM: QST: QsT SQL Tools (última versión estable - versión 0.4.2a). QST proporciona un mecanismo para generar consultas SQL simples: SELECCIONAR, INSERTAR, ELIMINAR, DESCUIDAR y EJECUTAR. La versión 0.4 usa T-SQL; la nueva versión - 0.5 - usará PostgreSQL por defecto. Encontrará este ORM basado en concepciones originales e inusuales. Por ejemplo, se integró con el sistema Qt Interview, por lo que puede configurar la representación de vista (anchos de columna, títulos) mucho más fácil.

Existen proyectos de ejemplo para las versiones 0.3 y 0.4: TradeDB 0.3, TradeDB 0.4. TradeDB 0.4 debería ser útil para comenzar a aprender QST.

0

Esto parece una excelente técnica. Sin embargo, tengo algunos problemas para compilar mi prototipo n enlace ...

Implementé los conceptos básicos como usted describe y llamé a la clase DAO para recuperar una instancia de uno de mis objetos residentes en DB.

Estas son las declaraciones de llamar a este modelo: estas clases

_db = <create QSqlDatabase>; 

    dao = new UniversalDAO (_db); 

    AddressDAO * aDAO = new AddressDAO (dao); 
    Address addr = aDAO->getByPrimaryKey(QVariant(1)); 

En mi AddressDAO.CPP, que tengo:

template<class Address> 
Address SpecializedDAO<Address>::getByPrimaryKey(const QVariant &key) 
{ } 

En el momento del enlace, me sale el siguiente:

undefined reference to 
`SpecializedDAO<Address>::getByPrimaryKey(QVariant const&)' 

¿Cómo iba a aplicar correctamente los métodos de la clase SpecializedDAO?

Actualización:

Estúpido de mí, estúpida, estúpida de mí .... Yo sobre todo tiene que esto funcione. Los problemas ....

  1. Mis clases del modelo (DTO) están envueltos en espacios de nombres y utilizar macros para definir y utilizar estos espacios de nombres. Además, traté de usar una buena jerarquía para estas clases y encontré que moc tiene un problema real con las jerarquías de clase envueltas en espacios de nombres ...

  2. Creo que las definiciones de función de las clases de plantilla deben estar en el archivo de encabezado - no puede estar en unidades de compilación separadas.

  3. qmake no trata muy bien las dependencias (archivo de cabecera) cuando se cruzan los límites de la biblioteca. Tengo mis cosas de modelo en una lib compartida y la función 'main()' (en un directorio separado) estaba tratando de leer un registro de la base de datos. El 'main()' archivo C fue no conseguir re-compilado cuando cambié mi archivo de cabecera de la clase modelo ...

Aquí hay más detalles:

En SpecializedDAO.h:

template<class DTO> 
DTO SpecializedDAO<DTO>::getByPrimaryKey(const QVariant &key) 
     throw (FlowException) 
{ 
    DTO obj; 
    dao->getByPrimaryKey(static_cast<QObject &> (obj), key); 
    return obj; 
} 

en UniversalDAO.cpp:

void 
UniversalDAO::getByPrimaryKey (QObject & dto, const QVariant & key) 
{ 
    <retrieve properties from 'dto' n build up QSqlQuery> 
    <execute QSqlQuery 'SELECT...' to retrieve record> 
    <call dto.setProperty() on all fields> 
} 

Una cuestión pendiente actual es el uso de tipos definidos por el usuario para los tipos de propiedad en mi Clases de DTO. Estoy tratando de usar std::string frente a QString, pero no importa lo que intenté (Q_DECLARE_METATYPE(std::string), qRegisterMetaType<std::string>(), etc., nada parecía funcionar ... tuve que volver a los tipos basados ​​en Qt. bummer ....