Android: SQLite один-ко-многим дизайн - PullRequest
11 голосов
/ 05 февраля 2010

Кто-нибудь может посоветовать, как реализовать сопоставление «один ко многим» для SQLite с использованием ContentProvider? Если вы посмотрите на Uri ContentProvider#insert(Uri, ContentValues), вы увидите, что у него есть ContentValues параметр, который содержит данные для вставки. Проблема в том, что в текущей реализации ContentValues не поддерживает метод put(String, Object), а класс является окончательным, поэтому я не могу его расширить. Почему это проблема? Вот мой дизайн:

У меня есть 2 таблицы, которые связаны между собой. Чтобы представить их в коде у меня есть 2 объекта модели. 1st представляет основную запись и имеет поле, которое является списком экземпляров второго объекта. Теперь у меня есть вспомогательный метод в объекте модели № 1, который возвращает ContentValues, сгенерированный из текущего объекта. Заполнить примитивные поля с помощью ContentValues#put перегруженных методов тривиально, но мне не повезло с этим списком. Так что в настоящее время, так как моя вторая строка таблицы представляет собой просто одно значение String, я генерирую строку с разделителями-запятыми, которую затем возвращаю String [] внутри 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();
    }

}

Ответы [ 3 ]

6 голосов
/ 05 февраля 2010

Я думаю, что вы смотрите на неправильный конец отношений один-ко-многим.

Взгляните, например, на поставщика контента ContactsContract. Контакты могут иметь много адресов электронной почты, много телефонных номеров и т. Д. Это достигается путем вставки / обновления / удаления на стороне "много" . Чтобы добавить новый номер телефона, вы вводите новый номер телефона, предоставляя идентификатор контакта, для которого этот номер телефона принадлежит.

Вы бы сделали то же самое, если бы у вас была простая база данных SQLite без поставщика контента. Отношения «один ко многим» в реляционных базах данных достигаются посредством вставок / обновлений / удалений в таблицу для стороны «многие», каждый из которых имеет внешний ключ обратно в сторону «один».

Теперь, с точки зрения ОО, это не идеально. Вы можете создавать объекты-оболочки в стиле ORM (например, Hibernate), которые позволяют вам управлять коллекцией дочерних элементов со стороны «один». Достаточно интеллектуальный класс коллекции может затем развернуться и синхронизировать таблицу «многие» для соответствия. Однако это не обязательно тривиально для правильной реализации.

5 голосов
/ 02 декабря 2011

Вы можете использовать ContentProviderOperations для этого.

В основном это массовые операции с возможностью обратной ссылки на идентификаторы, сгенерированные для родительских строк.

Как ContentProviderOperations может использоваться для дизайна «один ко многим», очень хорошо объяснено в этом ответе: Какова семантика withValueBackReference?

4 голосов
/ 06 февраля 2010

Итак, я собираюсь ответить на свой вопрос. Я был на правильном пути, имея две таблицы и два модельных объекта. Чего не хватало и что меня смущало, так это то, что я хотел напрямую вставить сложные данные через ContentProvider#insert в один вызов. Это не верно. ContentProvider должен создавать и поддерживать эти две таблицы, но решение о том, какую таблицу использовать, должно определяться параметром Uri ContentProvider#insert. Очень удобно использовать ContentResolver и добавлять методы, такие как «addFoo», к объекту модели. Такой метод будет принимать параметр ContentResolver и в конце приведена последовательность вставки сложной записи:

  1. Вставить родительскую запись через ContentProvider#insert и получить идентификатор записи
  2. Для каждого ребенка укажите идентификатор родителя (ключ foregn) и используйте ContentProvider#insert с другим Uri для вставки дочерних записей

Таким образом, единственный оставшийся вопрос - как конвертировать вышеуказанный код в транзакцию?

...