Android: SQLite uno-a-muchos diseño
-
18-09-2019 - |
Pregunta
Cualquier persona tiene buenos consejos sobre cómo poner en práctica uno-a-muchos de mapeo para SQLite
usando ContentProvider
? Si nos fijamos en Uri ContentProvider#insert(Uri, ContentValues)
se puede ver que se ha ContentValues
parámetro que contiene los datos a insertar. El problema es que en su actual ContentValues
aplicación no es compatible con el método put(String, Object)
y la clase es final así que no puedo extenderla. ¿Por qué es un problema? Aquí viene mi diseño:
Tengo 2 tablas que se encuentran en uno-a-muchos relación. Para representar estos valores en código que tengo 2 objetos del modelo. Primero representa el registro principal y tiene un campo que es una lista de instancias de objetos 2do. Ahora tengo un método de ayuda en el modelo de objetos # 1 que devuelve ContentValues
genera fuera del objeto actual. Es trivial para rellenar un campo con métodos primitivos ContentValues#put
sobrecargado, pero estoy fuera de suerte para la lista. Así que actualmente ya que mi segunda fila de la tabla es sólo un único valor de cadena genero una cadena delimitada por comas que luego de reanálisis a String [] en el interior ContentProvider#insert
. Eso se siente asco, así que tal vez alguien puede sugerir la forma en que se puede hacer de una manera más limpia.
Aquí hay algo de código. 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 del método ContentProvider#insert
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();
}
}
Solución
Creo que usted está buscando en el lado equivocado de la relación de uno a muchos.
Tome una mirada en el proveedor de contenidos 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 la lado "varios" . Para añadir un nuevo número de teléfono, se inserta un nuevo número de teléfono, proporcionando una identificación del contacto para el cual el número de teléfono pertenece.
Se podría hacer lo mismo si tuviera una base de datos SQLite llano con ningún proveedor de contenido. Uno-a-muchas relaciones en bases de datos relacionales se logran a través de inserciones / actualizaciones / eliminaciones en una mesa para el lado "varios", cada uno con una clave externa de nuevo al lado "uno".
Ahora bien, desde un punto de vista orientado a objetos, esto no es lo ideal. Usted es bienvenido para crear objetos envolventes de tipo ORM Hibernate (piensa) que le permiten manipular una colección de los niños desde el lado "uno". Una clase de colección lo suficientemente inteligente puede entonces dar la vuelta y sincronizar la tabla "varios" de igualar. Sin embargo, éstas no son necesariamente trivial para aplicar correctamente.
Otros consejos
Puede utilizar ContentProviderOperations
para esto.
Son básicamente las operaciones a granel con la capacidad de hacer una copia de referencia a los identificadores generados por filas primarias.
¿Cómo ContentProviderOperations
se puede utilizar para un diseño de uno a muchos está muy bien explicado en esta respuesta: ¿Cuáles son la semántica de withValueBackReference?
Así que voy a responder a mi propia pregunta. Yo estaba en el camino correcto con tener dos mesas y dos objetos del modelo. Lo que faltaba y lo que me confunde es que quería insertar directamente datos complejos a través ContentProvider#insert
en una sola llamada. Esto está mal. ContentProvider
debe crear y mantener estas dos mesas pero la decisión sobre la que debe ser dictado por el parámetro de Uri ContentProvider#insert
mesa de usar. Es muy conveniente utilizar ContentResolver y añadir métodos tales 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:
- Insertar registro padre a través
ContentProvider#insert
y obtener registro id - Por cada niño proporcionar una identificación con el padre (clave foregn) y utilizar
ContentProvider#insert
con diferentes Uri para insertar registros secundarios
Así que la única cuestión pendiente es cómo envuelva el código anterior en la transacción?