Android: SQLite One-To-Many Design
-
18-09-2019 - |
Вопрос
У кого-нибудь есть хороший совет о том, как реализовать картирование с одним ко многим SQLite
с использованием ContentProvider
? Если вы посмотрите на Uri ContentProvider#insert(Uri, ContentValues)
Вы можете видеть, что это ContentValues
парамет, который содержит данные для вставки. Проблема в том, что в своей текущей реализации ContentValues
не поддерживается put(String, Object)
Метод и класс являются окончательными, поэтому я не могу его расширить. Почему это проблема? Вот мой дизайн:
У меня есть 2 таблицы, которые находятся в отношениях от одного ко многим. Чтобы представить их в коде, у меня есть 2 модели объекта. 1 -й представляет основную запись и имеет поле, которое представляет собой список 2 -го экземпляров объекта. Теперь у меня есть вспомогательный метод в объекте модели № 1, который возвращает ContentValues
сгенерировано от текущего объекта. Тривиально заполнять примитивные поля ContentValues#put
перегруженные методы, но мне не повезло в список. Так что в настоящее время, так как моя 2 -я строка таблицы - это всего лишь одно строковое значение, я генерирую строку с разграниченной запятой ContentProvider#insert
. Анкет Это чувствует себя самости, так что, возможно, кто -то может намекнуть, как это можно сделать в чистоте.
Вот какой -то код. Сначала из модельного класса:
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 */}
И вот стержнятая версия 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();
}
}
Решение
Я думаю, что вы смотрите на неправильный конец отношений с одним ко многим.
Взглянуть на ContactsContract
Поставщик контента, например. Контакты могут иметь много адресов электронной почты, много телефонов и т. Д. "много" сторона. Чтобы добавить новый номер телефона, вы вставляете новый номер телефона, предоставляя идентификатор контакта, для которого относится номер телефона.
Вы бы сделали то же самое, если бы у вас была простая база данных SQLite без поставщика контента. Отношения с одним ко многим в реляционных базах данных достигаются посредством вставки/обновлений/удалений на таблице для «многих» стороной, каждый из которых имеет внешний ключ обратно на «единую» сторону.
Теперь, с точки зрения OO, это не идеально. Вы можете создать объекты обертки в стиле ORM (подумайте о спячке), которые позволяют вам манипулировать коллекцией детей с «одной» стороны. Достаточно разветвленный класс сбора может затем обойти и синхронизировать таблицу «много», чтобы соответствовать. Однако они не обязательно тривиальны для правильного реализации.
Другие советы
Вы можете использовать ContentProviderOperations
для этого.
Они в основном представляют собой объемные операции с возможностью обратной ссылки на идентификаторы, генерируемые для родительских строк.
Как ContentProviderOperations
может быть использован для дизайна от одного ко многим, очень хорошо объяснен в этом ответе: Какова семантика в счете
Так что я собираюсь ответить на свой вопрос. Я был на правильном пути с двумя таблицами и двумя модельными объектами. Чего не хватало и что меня смутило, так это то, что я хотел напрямую вставить сложные данные через ContentProvider#insert
в одном вызове. Это не правильно. ContentProvider
должен создать и поддерживать эти две таблицы, но решение о том, какая таблица использовать, должно быть продиктовано параметром URI ContentProvider#insert
. Анкет Очень удобно использовать ContentResolver и добавлять такие методы, как «AddFoo» в объект модели. Такой метод будет принимать параметр ContentReSolver, и в конце вот последовательность для вставки сложной записи:
- Вставить родительский рекорд через
ContentProvider#insert
и получить идентификатор записи - В каждом ребенке предоставляют родительский идентификатор (Foregn Key) и используйте
ContentProvider#insert
с разным URI для вставки детских записей
Итак, единственный оставшийся вопрос - как охватить приведенный выше код в транзакции?