Android: Diseño de uno-a-muchos de SQLite

¿Alguien tiene buenos consejos sobre cómo implementar la asignación uno-a-muchos para SQLite usando ContentProvider ? Si mira Uri ContentProvider#insert(Uri, ContentValues) puede ver que tiene el ContentValues ContentValues ​​que contiene los datos a insertar. El problema es que en su implementación actual ContentValues no soporta el método put(String, Object) y la clase es final así que no puedo extenderlo. ¿Por qué es un problema? Aquí viene mi diseño:

Tengo 2 mesas que están en una relación de uno a muchos. Para representar estos en el código tengo 2 objetos de modelo. 1ª representa el registro principal y tiene un campo que es una lista de instancias de segundo objeto. Ahora tengo un método auxiliar en el objeto de 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. Por lo tanto, ya que desde la segunda fila de la tabla es sólo un valor de una sola cadena que genera una cadena delimitada por coma que luego repaso a String [] dentro de ContentProvider#insert . Que se siente yucky, así que tal vez alguien puede sugerir cómo se puede hacer de manera más limpia.

Aquí hay un código. Primero de la clase de 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á la versión adelgazada de ContentProvider#insert method

 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(); } } 

Pienso que usted está mirando el extremo incorrecto de la relación 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 está haciendo inserciones / actualizaciones / eliminaciones en el lado "muchos" . Para agregar un nuevo número de teléfono, inserta un nuevo número de teléfono, proporcionando un ID del contacto para el que pertenece el número de teléfono.

Haría lo mismo si tuviera una base de datos 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 uno con una clave externa de vuelta al lado "uno".

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

Puede utilizar ContentProviderOperations para esto.

Son básicamente operaciones a granel con la capacidad de volver a referenciar a los identificadores generados para las filas de los padres.

Cómo ContentProviderOperations se puede utilizar para un diseño uno-a-muchos está muy bien explicado en esta respuesta: ¿Cuáles son la semántica de withValueBackReference?

Así que voy a contestar mi propia pregunta. Yo 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 de ContentProvider#insert en una sola llamada. Esto está mal. ContentProvider debe crear y mantener estas dos tablas, pero la decisión sobre qué tabla utilizar debe ser dictada por el parámetro Uri de ContentProvider#insert . Es muy conveniente usar ContentResolver y agregar métodos como "addFoo" al objeto de modelo. Tal método tomaría el parámetro ContentResolver y al final aquí está la secuencia para insertar un registro complejo:

  1. Insertar registro principal a través de ContentProvider#insert y obtener el ID de registro
  2. Por cada niño proporcione ID de padre (clave de foregn) y use ContentProvider#insert con Uri diferente para insertar registros de hijo

Así que la única cuestión que queda es cómo sobre el código anterior en la transacción?

  • ¿El depurador de Android trunca mensajes de depuración?
  • Android Studios: Android Device Manager no muestra archivos para Nougat 7.0 en el Explorador de archivos
  • cómo crear base de datos una sola vez, a continuación, leer y escribir varias veces desde él, SQLite, OpenHelper, Android
  • ¿Está correcta la documentación de Android Cursor.moveToNext ()?
  • Actualización de base de datos y aplicaciones Android SQLite
  • Restaurar archivo DB de SQLite
  • Aclaración sobre el row_id devuelto por la instrucción de inserción de sqlite
  • Cómo proteger una base de datos sqlite pre hecho en android?
  • Ejemplo de cómo implementar ALTER TABLE
  • ¿Cómo saber todas las conexiones abiertas de la base de datos usando DAO verde ..?
  • Recibo frecuentemente SQLiteDoneException al consultar un valor, ¿por qué?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.