Android: SQLite uno-a-molti disegno
-
18-09-2019 - |
Domanda
Qualcuno ha buoni consigli su come implementare uno-a-molti mapping per SQLite
utilizzando ContentProvider
? Se si guarda a Uri ContentProvider#insert(Uri, ContentValues)
si può vedere che ha ContentValues
param che contiene i dati da inserire. Il problema è che nella sua attuazione ContentValues
attuale non supporta metodo put(String, Object)
e categoria è definitiva quindi non posso estenderlo. Perché è un problema? Ecco che arriva il mio progetto:
Ho 2 tavoli che si trovano in uno-a-molti. Per rappresentare questi in codice che ho 2 oggetti del modello. 1 ° rappresenta il record principale e dispone di un campo che è un elenco di istanze di oggetti 2 °. Ora ho un metodo di supporto nel modello a oggetti # 1 che restituisce ContentValues
generato fuori l'oggetto corrente. E 'banale per popolare un campo primitivo con metodi ContentValues#put
sovraccarico ma sono fuori di fortuna per la lista. Quindi al momento dato che il mio 2 ° riga della tabella è solo un singolo valore String ho generare una stringa delimitata da virgole che poi ho di analisi per String [] all'interno ContentProvider#insert
. Che si sente schifo, così forse qualcuno può suggerire come può essere fatto in modo più pulito.
Ecco un po 'di codice. In primo luogo dalla classe del modello:
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 */}
ed ecco snellita versione del metodo 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();
}
}
Soluzione
Credo che si sta guardando dalla parte sbagliata della relazione uno-a-molti.
Date un'occhiata al fornitore di contenuti ContactsContract
, per esempio. I contatti possono avere molti indirizzi e-mail, molti numeri di telefono, ecc Il modo che si compie è quello di eseguire inserti / aggiornamenti / eliminare il "molti" lato. Per aggiungere un nuovo numero di telefono, si inserisce un nuovo numero di telefono, che fornisce un ID del contatto per i quali il numero di telefono appartiene.
Si potrebbe fare lo stesso se si ha un database SQLite normale senza fornitore di contenuti. Uno-a-molti in database relazionali vengono raggiunti tramite inserti / aggiornamenti / cancella su un tavolo per il lato "molti", ciascuno con una chiave esterna di nuovo al lato "uno".
Ora, dal punto di vista OO, questo non è l'ideale. Siete invitati a creare oggetti wrapper ORM in stile (si pensi Hibernate) che consentono di manipolare una raccolta di bambini dal lato "uno". Una classe di raccolta sufficientemente-intelligente può poi girarsi e sincronizzare il "più" tabella per abbinare. Tuttavia, questi non sono necessariamente banali da implementare correttamente.
Altri suggerimenti
È possibile utilizzare ContentProviderOperations
per questo.
sono operazioni fondamentalmente massa con la possibilità di eseguire riferimento agli identificatori generati per righe padre.
Come ContentProviderOperations
può essere utilizzato per un design uno-a-molti è molto ben spiegato in questa risposta: Quali sono la semantica di withValueBackReference?
Quindi ho intenzione di rispondere alla mia domanda. Io ero sulla strada giusta con avere due tavoli e due oggetti del modello. Ciò che mancava e che cosa confuso mi era che volevo inserisco direttamente dati complessi attraverso ContentProvider#insert
in una singola chiamata. Questo è sbagliato. ContentProvider
dovrebbe creare e mantenere queste due tabelle, ma la decisione su quale tabella da utilizzare deve essere dettata dal parametro Uri di ContentProvider#insert
. E 'molto comodo da usare ContentResolver e aggiungere metodi come "addFoo" all'oggetto modello. Tale metodo richiederebbe parametro ContentResolver e alla fine qui sono la sequenza per inserire un record complesso:
- Inserisci record padre attraverso
ContentProvider#insert
e ottenere il record id - Per ogni bambino fornire ID genitore (chiave foregn) e utilizzare
ContentProvider#insert
con diversi Uri per inserire record figlio
Quindi l'unica domanda che rimane è come busta il codice di cui sopra in transazione?