2010-02-05 25 views
10

¿Alguien tiene buenos consejos sobre cómo implementar el mapeo uno a muchos para SQLite usando ContentProvider? Si mira Uri ContentProvider#insert(Uri, ContentValues), puede ver que tiene el parámetro ContentValues que contiene datos para insertar. El problema es que en su implementación actual ContentValues no es compatible con el método put(String, Object) y la clase es definitiva, así que no puedo extenderla. ¿Por qué es un problema? Aquí viene mi diseño:Android: SQLite de diseño uno a muchos

Tengo 2 tablas que están en una relación uno a muchos. Para representarlos en el código, tengo 2 objetos modelo. Primero representa el registro principal y tiene un campo que es una lista de instancias de segundo objeto. Ahora tengo un método de ayuda en el objeto modelo # 1 que devuelve ContentValues generado fuera del objeto actual. Es trivial poblar campos primitivos con ContentValues#put métodos sobrecargados, pero no tengo suerte para la lista. Así que actualmente dado que mi segunda fila de la tabla es solo un único valor de cadena, genero una cadena delimitada por comas que luego vuelvo a analizar en String [] dentro de ContentProvider#insert. Eso se siente asqueroso, así que tal vez alguien pueda insinuar cómo se puede hacer de una manera más limpia.

Aquí hay algunos códigos. En primer lugar de la clase del modelo:

public ContentValues toContentValues() { 
    ContentValues values = new ContentValues(); 
    values.put(ITEM_ID, itemId); 
    values.put(NAME, name); 
    values.put(TYPES, concat(types)); 
    return values; 
} 

private String concat(String[] values) { /* trivial */} 

y aquí está adelgazado versión de ContentProvider#insert método

public Uri insert(Uri uri, ContentValues values) { 
    SQLiteDatabase db = dbHelper.getWritableDatabase(); 
    db.beginTransaction(); 
    try { 
     // populate types 
     String[] types = ((String)values.get(Offer.TYPES)).split("|"); 
     // we no longer need it 
     values.remove(Offer.TYPES); 
     // first insert row into OFFERS 
     final long rowId = db.insert("offers", Offer.NAME, values); 
     if (rowId > 0 && types != null) { 
      // now insert all types for the row 
      for (String t : types) { 
       ContentValues type = new ContentValues(8); 
       type.put(Offer.OFFER_ID, rowId); 
       type.put(Offer.TYPE, t); 
       // insert values into second table 
       db.insert("types", Offer.TYPE, type); 
      } 
     } 
     db.setTransactionSuccessful(); 
     return ContentUris.withAppendedId(Offer.CONTENT_URI, rowId); 
    } catch (Exception e) { 
     Log.e(TAG, "Failed to insert record", e); 
    } finally { 
     db.endTransaction(); 
    } 

} 
+0

Sí, tengo el mismo problema ... cómo envuelva múltiples insertos en una transacción? Logré esto agregando dos URI especiales: uno para el inicio de la transacción y otro para el final. Sé que esta no es la mejor solución. Si tienes uno mejor lo agradeceré! – Bhiefer

Respuesta

5

Creo que usted está buscando en el lado equivocado de la relación de uno a muchos.

Eche un vistazo al proveedor de contenido ContactsContract, por ejemplo. Los contactos pueden tener muchas direcciones de correo electrónico, muchos números de teléfono, etc. La forma en que se logra es haciendo inserciones/actualizaciones/eliminaciones en el lado "muchos". Para agregar un nuevo número de teléfono, inserte un nuevo número de teléfono y proporcione una identificación del contacto al que pertenece el número de teléfono.

Haría lo mismo si tuviera una base de datos simple SQLite sin proveedor de contenido. Las relaciones uno a muchos en las bases de datos relacionales se logran a través de inserciones/actualizaciones/eliminaciones en una tabla para el lado "muchos", cada una de las cuales tiene una clave externa que regresa al lado "uno".

Ahora, desde el punto de vista OO, esto no es ideal. Le invitamos a crear objetos de envoltura estilo ORM (piense en Hibernate) que le permiten manipular una colección de niños del lado "uno". Una clase de colección suficientemente inteligente puede dar la vuelta y sincronizar la tabla "muchos" para que coincida. Sin embargo, estos no son necesariamente triviales para implementarse correctamente.

+0

Bueno, tengo UN registro principal, decir Persona que hace referencia a MUCHOS teléfonos munbers. Así que inserto el registro de Persona y obtengo su clave, luego inserto un poco de registro en la tabla de Teléfono, proporcionando a cada uno la clave externa de Persona. Bastante estándar. Entonces no tengo ningún problema para representar esta relación en el lado de Java con dos de mis objetos modelo y una colección de teléfonos en el objeto Person. Donde tengo dificultades es conectarlos a la API del proveedor. Voy a estudiar ContactsContract, gracias por la pista e informar de nuevo – Bostone

+0

En realidad, mis inserciones no son dinámicas, básicamente analizo XML e inserto registros. Una vez allí, solo leeré estos sin más manipulación. Supongo que puedo usar un ContentResolver por mesa, pero también quiero exponer esta funcionalidad como ContentProvider y ahí es donde de nuevo estoy teniendo dificultades – Bostone

+0

En realidad estoy pasando por la clase Contacts en 1.5 - gracias de nuevo por la sugerencia, creo que es exactamente lo que necesito – Bostone

4

Así que voy a responder mi propia pregunta. Estaba en el camino correcto con dos mesas y dos objetos modelo. Lo que faltaba y lo que me confundía era que quería insertar directamente datos complejos a través del ContentProvider#insert en una sola llamada. Esto está mal. ContentProvider debe crear y mantener estas dos tablas, pero la decisión sobre qué tabla usar debe estar dictada por el parámetro Uri de ContentProvider#insert. Es muy conveniente utilizar ContentResolver y agregar métodos como "addFoo" al objeto modelo.Tal método sería tomar parámetro ContentResolver y al final aquí son la secuencia para insertar un registro complejo: registro padre

  1. Insertar través ContentProvider#insert y obtener ID de registro
  2. Per cada niño proporcionar ID padre (clave foregn) y el uso ContentProvider#insert con diferentes URI para insertar registros secundarios

Entonces, ¿la única pregunta restante es cómo envolver el código anterior en la transacción?

5

Puede usar ContentProviderOperations para esto.

Básicamente son operaciones masivas con la capacidad de retroceder la referencia a los identificadores generados para las filas primarias.

Cómo ContentProviderOperations se puede utilizar para un diseño de uno a muchos está muy bien explicado en esta respuesta: What are the semantics of withValueBackReference?

Cuestiones relacionadas