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.
- Populating Spinner de la base de datos SQLite Android
- Debería llamar a .close () en SQLiteDatabase y SQLiteOpenHelper
- Base de datos no copiar desde activos
- Tabla normal vs tabla virtual SQLite DB
- SQLite, ¿cómo obtener todos los nombres de tablas en la base de datos?
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(); } }
- ¿Qué tan rápido es cursor.moveToPosition (i)?
- En Android db de SQLite cómo coincidir con valores de columnas particulares que tienen id mismo en varias tablas de una base de datos?
- Cómo convertir de java.util.HashMap a android.content.ContentValues?
- Buscar datos en la base de datos SQLite de Android
- La aplicación se bloquea al iniciar Debido a java.lang.IllegalArgumentException: la columna '_id' no existe
- Android / ORMLite Insertar fila con ID
- Mantener los datos SQLite después de la actualización
- Cómo evitar que se inserte valor duplicado en sqlite databse (si se duplican y luego se sobreescriben)
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:
- Insertar registro principal a través de
ContentProvider#insert
y obtener el ID de registro - 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?
- ¿Hay una descripción gráfica de todo el elemento GUI de Android?
- ¿Por qué Android utiliza Ints en vez de Enums?