Доставчик на съдържание за Android SQLite Junction Tables

Внедрявам доставчик на съдържание, който е подкрепен от доста сложна SQLite DB схема. Базата данни има няколко съединителни таблици и не съм сигурен дали те трябва да бъдат видими за потребителя на доставчика на съдържание или не.

Родителските таблици са изложени чрез 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 само като за интерфейс към база данни, а за Договора като за схема на база данни. ContentProvider е много по-гъвкав и мощен от това. Основната разлика е, че ContentProviders се адресират чрез URI, докато в SQL имате само имена на таблици. За разлика от името на таблица, URI има структура. URI имат път, който идентифицира обекта (или директорията с обекти), с които искате да работите. Също така можете да добавите параметри на заявка към URI, за да промените поведението на операция. В това отношение ContentProvider може да бъде проектиран подобно на RESTful услуга.

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

Вмъкнете нов филм

вмъкване на /филми/

Стойности: {"movie_id": <movie-id>, "title": <movie-title>, "year": ...}

Вмъкнете нов актьор

вмъкване на /актори/

Стойности: {"actor_id": <actor-id>, "name": <actor-name>, "gender": ...}

Добавяне на съществуващ актьор към филм

вмъкнете в /movies/movie-id/actors/

Стойности: {"actor_id": <actor-id>}

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

вмъкнете в /актьори/идентификатор на актьор/филми/

Стойности: {"movie_id": <movie-id>}

По избор: добавете нов актьор директно към филм:

вмъкнете в /movies/movie-id/actors/

Стойности: {"actor_id": <actor-id>, "name": <actor-name>, "gender": ... }

Ако не съществува актьор с дадения идентификатор, тази операция ще създаде новия актьор и ще го свърже с филма в една стъпка. Ако актьор с този идентификатор вече съществува, ще бъде хвърлено изключение. Същото може да се направи и обратното, като се добави нов филм към актьор.

Изтриване на актьор от филм

изтриване на /movies/movie-id/actors/actor-id

or

изтриване на /actors/actors-id/movies/movie-id

Вземете всички филми

запитване за /филми/

Вземете конкретен филм

заявка за /movies/movie-id

Накарайте всички актьори да играят в конкретен филм

заявка за /movies/movie-id/actors/

Вземете всички филми, в които е играл определен актьор

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

Незадължителният оператор за избор на заявка може да се използва за филтриране на резултата. За да получите филми от последните 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