Соединительные таблицы SQLite поставщика контента для Android

Я реализую поставщика контента, который поддерживается довольно сложной схемой базы данных SQLite. В базе данных есть несколько соединительных таблиц, и я не уверен, должны ли они быть видны пользователю контент-провайдера или нет.

Родительские таблицы доступны через Contract, каждая из них имеет свой собственный URI содержимого и т. д. Теперь при вставке данных с помощью метода ContentResolver#applyBatch() я создаю ContentProviderOperation для каждого URI содержимого таблицы. Пока все ясно. Но моя проблема в том, как должны быть заполнены соединительные таблицы, поскольку у них нет собственных URI контента?

Чтобы проиллюстрировать это, вот пример. У меня есть 2 "родительские" таблицы, Movies и Actors. Между ними существует связь «многие ко многим», поэтому у меня есть соединительная таблица с именем MoviesActors.

Чтобы вставить в одну партию, я делаю следующее:

List<ContentProviderOperation> operations = new ArrayList<>;
// movie
operations.add(ContentProviderOperation.newInsert(Contract.Movie.ContentUri).withValue("movie_id", "23asd2kwe0231123sa").build());
// actor
operations.add(ContentProviderOperation.newInsert(Contract.Actor.ContentUri).withValue("actor_id", "89asd02kjlwe081231a").build());
getContentResolver().applyBatch(authority, operations);

Таблица соединений MoviesActors должна быть вставлена ​​со строкой, содержащей movie_id и actor_id. Как мне позаботиться о соединительной таблице в этой ситуации?

Единственное, что приходит мне на ум, это расширить Контракт, чтобы URI контента указывал на соединительные таблицы, и добавить еще один ContentProviderOperation, иначе как вы связываете movie_id и actor_id с ContentProvider#applyBatch()?

Я предпочитаю не показывать соединительную таблицу пользователю ContentProvider, но здесь я могу ошибаться... возможно, именно так это должно быть сделано на Android?

Я искал эту тему уже несколько дней и не нашел ответа на этот вопрос. Любая помощь будет принята с благодарностью.

Дополнительный вопрос:

Нужно ли выставлять каждую таблицу через контракт? Например, при наличии дочерних таблиц в отношениях «один ко многим». Я специально имею в виду Insert/Update/Delete, так как знаю, что с Query я могу просто выполнить соединение, но, возможно, я ошибаюсь и здесь.

Большое спасибо!

ПРИМЕЧАНИЕ. Меня не интересуют решения сторонних библиотек.


person Nimrod Dayan    schedule 12.12.2015    source источник


Ответы (1)


Мне кажется, вы подходите к проблеме не с той стороны. Вы пытаетесь разработать интерфейс, соответствующий структуре вашей базы данных, но интерфейс должен быть на первом месте.

Во-первых, интерфейс должен соответствовать всем требованиям вашего клиента ContentProvider. Если вашему клиенту ContentProvider требуется доступ к соединительной таблице, вам придется предоставить его (каким-то образом, см. ниже), в противном случае вам это не нужно. Хороший интерфейс скрывает фактические детали реализации, поэтому клиенту ContentProvider не нужно заботиться о том, поддерживается ли ContentProvider базой данных SQLite, кучей карт в памяти или даже веб-службой.

Кроме того, вы не должны думать о ContentProvider просто как о интерфейсе к базе данных, а о Contract — как о схеме базы данных. ContentProvider гораздо более универсален и мощен. Основное отличие состоит в том, что ContentProviders адресуются по URI, тогда как в SQL у вас есть только имена таблиц. В отличие от имени таблицы, URI имеет структуру. URI имеют путь, который идентифицирует объект (или каталог объектов), с которым вы хотите работать. Также вы можете добавить параметры запроса к URI, чтобы изменить поведение операции. В этом отношении ContentProvider может быть разработан так же, как служба RESTful.

Ниже приведен конкретный (но неполный) пример контракта простой базы данных фильмов. По сути, это то, как можно спроектировать веб-службу RESTful, за исключением одного: как и в вашем коде, movie-id и actor- id предоставляется вызывающим абонентом. Настоящая служба RESTful будет создавать и назначать их автоматически и возвращать вызывающей стороне. ContentProvider может возвращать только long ID при вставке новых объектов.

Вставить новый фильм

вставить в /movies/

Значения: {"movie_id": <movie-id>, "title": <movie-title>, "year": ...}

Вставить нового актера

вставить в /actors/

Значения: {"actor_id": <actor-id>, "name": <actor-name>, "gender": ...}

Добавить существующего актера в фильм

вставьте в /movies/идентификатор фильма/actors/

Значения: {"actor_id": <actor-id>}

Добавить существующий фильм к актеру:

вставьте в /actors/идентификатор актера/movies/

Значения: {"movie_id": <movie-id>}

Необязательно: добавьте нового актера прямо в фильм:

вставьте в /movies/идентификатор фильма/actors/

Значения: {"actor_id": <actor-id>, "name": <actor-name>, "gender": ... }

Если актер с данным идентификатором не существует, эта операция создаст нового актера и свяжет его с фильмом за один шаг. Если актор с таким идентификатором уже существует, будет выдано исключение. То же самое можно сделать и наоборот, добавив актеру новый фильм.

Удалить актера из фильма

удалить в /movies/идентификатор_фильма/actors/идентификатор_актера

or

удалить в /actors/actors-id/movies/movie-id

Получить все фильмы

запрос в /movies/

Получить конкретный фильм

запрос в /movies/идентификатор фильма

Получить всех актеров, играющих в определенном фильме

запрос в /movies/идентификатор фильма/actors/

Получить все фильмы, в которых играл конкретный актер

запрос к /actors/идентификатору актера/movies/

Необязательный оператор выбора запроса можно использовать для фильтрации результата. Чтобы получить фильмы за последние 10 лет, в которых играл конкретный актер, вы должны добавить выбор movies_year>=2005 к последнему запросу.

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

Работа ContentProvider заключается в сопоставлении этих операций с базой данных или любой другой серверной частью.

person Marten    schedule 29.12.2015
comment
Спасибо за подробный ответ. Я пришел к такому же решению после прочтения кодовой базы Google I/O 2015, в частности, всех классов в пакете github.com/google/iosched/tree/master/android/src/main/java/com/ - person Nimrod Dayan; 31.12.2015