Android: SQLite Eins-zu-Viele-Design
-
18-09-2019 - |
Frage
Jeder hat gute Ratschläge zur Implementierung von Eins-zu-Viele-Mapping für SQLite
Verwendung ContentProvider
? Wenn Sie sich ansehen Uri ContentProvider#insert(Uri, ContentValues)
Sie können sehen, dass es hat ContentValues
Param, der Daten enthält, die einfügen sollen. Das Problem ist, dass in seiner aktuellen Implementierung ContentValues
unterstützt nicht put(String, Object)
Methode und Klasse sind endgültig, daher kann ich sie nicht erweitern. Warum ist es ein Problem? Hier kommt mein Design:
Ich habe 2 Tabellen, die sich in einer eins-zu-Viele-Beziehung befinden. Um diese in Code darzustellen, habe ich 2 Modellobjekte. 1st repräsentiert den Hauptdatensatz und verfügt über ein Feld, das eine Liste der 2. Objektinstanzen ist. Jetzt habe ich eine Helfermethode im Modellobjekt Nr. 1, die zurückgibt ContentValues
vom aktuellen Objekt generiert. Es ist trivial, primitive Felder mit ContentValues#put
Überladene Methoden, aber ich habe kein Glück für die Liste. Da meine 2. Tabellenzeile derzeit nur ein einzelner String -Wert ist, generiere ich eine Comma -Abgrenzungsfolge, die ich dann zu String [] in Inside repariere ContentProvider#insert
. Das fühlt sich juck an, also kann jemand vielleicht darauf hinweisen, wie es auf sauberer Weise gemacht werden kann.
Hier ist ein Code. Zuerst aus der Modellklasse:
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 */}
Und hier ist die Version von verklemmt ContentProvider#insert
Methode
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();
}
}
Lösung
Ich denke, Sie schauen auf das falsche Ende der Eins-zu-Viele-Beziehung.
Schauen Sie sich das an die ContactsContract
Inhaltsanbieter zum Beispiel. Kontakte können viele E -Mail -Adressen, viele Telefonnummern usw. haben "viele" Seite. Um eine neue Telefonnummer hinzuzufügen, geben Sie eine neue Telefonnummer ein und geben eine ID des Kontakts an, für die sich die Telefonnummer bezieht.
Sie würden dasselbe tun, wenn Sie eine einfache SQLite -Datenbank ohne Inhaltsanbieter hätten. Eins-zu-Viele-Beziehungen in relationalen Datenbanken werden über Einfügungen/Updates/Löschungen auf einer Tabelle für die Seite "Viele" erreicht, wobei jeder einen Fremdschlüssel auf der "One" -Seite zurückgibt.
Aus OO -Sicht ist dies nicht ideal. Sie können herzlich eingestuft werden, um Wrapper-Objekte im Orm-Stil zu erstellen (denken Sie Hibernate), mit denen Sie eine Sammlung von Kindern von der "One" -Seite manipulieren können. Eine ausreichend intelligente Sammlungsklasse kann sich dann umdrehen und die übereinstimmende "Viele" -Tabelle synchronisieren. Diese sind jedoch nicht unbedingt trivial, um ordnungsgemäß umzusetzen.
Andere Tipps
Sie können verwenden ContentProviderOperations
dafür.
Sie sind im Grunde genommen Bulk-Operationen mit der Fähigkeit, die für Elternreihen generierten Kennungen zu beenden.
Wie ContentProviderOperations
Kann für ein Eins-zu-viele-Design verwendet werden, wird in dieser Antwort sehr gut erklärt: Was ist die Semantik von With ValueBackReference?
Also werde ich meine eigene Frage beantworten. Ich war auf dem richtigen Weg mit zwei Tischen und zwei Modellobjekten. Was fehlte und was mich verwirrte, war, dass ich direkt komplexe Daten durch einfügen wollte ContentProvider#insert
in einem einzigen Anruf. Das ist falsch. ContentProvider
Sollte diese beiden Tabellen erstellen und verwalten, aber die Entscheidung, welche Tabelle verwendet werden soll, sollte durch den URI -Parameter von diktiert werden ContentProvider#insert
. Es ist sehr bequem, ContentResolver zu verwenden und Methoden wie "Addfoo" zum Modellobjekt hinzuzufügen. Eine solche Methode würde den ContentResolver -Parameter aufnehmen und am Ende finden Sie hier die Reihenfolge, um einen komplexen Datensatz einzufügen:
- Fügen Sie den übergeordneten Datensatz durch
ContentProvider#insert
und Record ID erhalten - Pro Jedes Kind geben die übergeordnete ID (Vorgesetzte) und verwenden Sie die Verwendung
ContentProvider#insert
mit verschiedenen URI, um untergeordnete Datensätze einzulegen
Die einzige verbleibende Frage ist also, wie der obige Code in der Transaktion umhüllt.