Как устранить ошибку недопустимой схемы Room, включающую notNull и primaryKeyPosition в предварительно заполненной базе данных? - PullRequest
1 голос
/ 15 января 2020

Моя проблема : Я конвертирую существующее Java Настольное приложение и изучаю Android Разработка комнаты и получаю предварительно заполненную базу данных SQLite, созданную с помощью SQLite Studio, которая содержит множество таблица объединения многих (то есть Author_By_Source ). Эта база данных и таблица используются в существующем настольном приложении Java. Я пытаюсь устранить недопустимое несоответствие схемы между свойствами ' notNull ' и ' primaryKeyPosition '. Мне не удалось обновить эти два свойства. Это сегмент ошибки, который я пытаюсь устранить для поля AuthorID, но это также те же различия для SourceID в том же сообщении об ошибке. Таким образом, оба поля должны иметь одинаковое разрешение:

    Expected:
    TableInfo{name='Author_By_Source', columns={AuthorID=Column{name='AuthorID', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}, ...
    Found:
    TableInfo{name='Author_By_Source', columns={AuthorID=Column{name='AuthorID', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, ...

Что я пробовал : я пытался использовать интерфейс SQLite Studio, используемый для построения и структурирования базы данных, и редактировал структуру таблицы для Таблица * Author_By_Source . Я установил для каждого поля значение Not Null, но оно не отображается как «true» при повторном запуске приложения. Я также не определил, как обозначить primaryKeyPosition. Я попробовал обратное, более подробно прочитав варианты и методы аннотаций к комнатам, но не добился успеха. Я читал некоторую документацию Room по всему моему проекту и посещал некоторые форумы и учебные пособия, но не нашел ничего, что касалось именно этой спецификации c.

Что я пытаюсь сделать : Я пытаюсь использовать предварительно заполненную базу данных с несколькими таблицами, некоторые из которых имеют таблицы соединения для отношений «многие ко многим». Я надеюсь на какое-то направление.

Ниже я пояснил свой класс AuthorBySource, который может помочь:

 @Entity(tableName = "Author_By_Source", primaryKeys = {"AuthorID", "SourceID"},  foreignKeys = {
        @ForeignKey(entity = Authors.class, parentColumns = "AuthorID", childColumns = "AuthorID"),
        @ForeignKey(entity = Sources.class, parentColumns = "SourceID", childColumns = "SourceID")},
        indices = {@Index("AuthorID"), @Index("SourceID")})
public class AuthorBySource {
    @ColumnInfo(name = "AuthorID")
    private int authorID;
    @ColumnInfo(name = "SourceID")
    private int sourceID;

Ответы [ 2 ]

0 голосов
/ 27 января 2020

Мне удалось решить эту проблему, но в результате она сопоставила ограничения полей внешней базы данных с Android объектами комнаты и узнала немного больше об аннотациях комнат. Проблемы были в основном, было ли поле не Null. По какой-то причине, которая может быть наивным недоразумением с моей стороны, заключается в том, что при создании сущностей с Android комнатой поля, по-видимому, автоматически по умолчанию принимают значение «notNull», чего я лично не ожидал, если бы я не указал. Однако благодаря опыту я думаю, что у меня более стабильная база данных, чем у меня.

0 голосов
/ 15 января 2020

Вы повторно копируете файл ресурсов и удаляете приложение каждый раз, когда вносите изменения? Если нет, и это не решает проблему, то: -

Если вы компилируете код комнаты, посмотрите на java (сгенерированный) в your_database_class _impl (где your_database_class - это ваша @ База данных имя класса) и по методу createAllTables . Это SQL используется для создания базы данных (вы можете игнорировать room_master SQL).

Например (это основано на вашей сущности и минимальных ссылочных таблицах): -

  @Override
  public void createAllTables(SupportSQLiteDatabase _db) {
    _db.execSQL("CREATE TABLE IF NOT EXISTS `Authors` (`AuthorID` INTEGER, `AuthorName` TEXT, PRIMARY KEY(`AuthorID`))");
    _db.execSQL("CREATE TABLE IF NOT EXISTS `Sources` (`SourceID` INTEGER, `SourceName` TEXT, PRIMARY KEY(`SourceID`))");
    _db.execSQL("CREATE TABLE IF NOT EXISTS `Author_By_Source` (`AuthorID` INTEGER NOT NULL, `SourceID` INTEGER NOT NULL, PRIMARY KEY(`AuthorID`, `SourceID`), FOREIGN KEY(`AuthorID`) REFERENCES `Authors`(`AuthorID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`SourceID`) REFERENCES `Sources`(`SourceID`) ON UPDATE NO ACTION ON DELETE NO ACTION )");
    _db.execSQL("CREATE INDEX IF NOT EXISTS `index_Author_By_Source_AuthorID` ON `Author_By_Source` (`AuthorID`)");
    _db.execSQL("CREATE INDEX IF NOT EXISTS `index_Author_By_Source_SourceID` ON `Author_By_Source` (`SourceID`)");
    _db.execSQL("CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)");
    _db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '953f8512db886fb7d206fa561a7117c4')");
  }

Вам необходимо соответствующим образом преобразовать базу данных, то есть в соответствии с определением, например, Вы могли бы для вышеперечисленного в SQLite Studio использовать

/* Just once */
PRAGMA foreign_keys = 0;

/* Per table */
DROP TABLE IF EXISTS Author_By_Source_new;
DROP TABLE IF EXISTS Author_By_Source_original;
CREATE TABLE IF NOT EXISTS Author_By_Source_new copy_generated_sql_for_the_column_definitions_etc;
DELETE FROM Author_By_Source_new;
INSERT INTO Author_By_Source_new SELECT * FROM Author_By_Source;
ALTER TABLE Author_By_Source RENAME TO Author_By_Source_original;
ALTER TABLE Author_By_Source_new RENAME TO Author_By_Source;
/* IF HAPPY THEN DO */
-- DROP TABLE IF EXISTS Author_By_Source_original; /* NOTE TURNED OFF */

...... do the equivalent for all tables


/* Just once */
PRAGMA foreign_keys = 1;
PRAGMA foreign_key_check;
PRAGMA integrity_check; 
  • copy_generated_sql_for_the_column_definitions_et c должны быть соответственно заменены частью определения столбца из сгенерированного SQL (или скопируйте весь оператор и измените имя таблицы).
  • Обратите внимание , что оператор INSERT INTO .... предполагает, что порядок столбцов одинаков для новых и исходных таблиц, в противном случае используйте INSERT INTO (column_list).... где столбцы соответствуют сгенерированным SQL например INSERT INTO Author_By_Source_new (AuthorID,SourceID) SELECT * FROM Author_By_Source;
  • CREATE TABLE SQL будет (на основе сгенерированного SQL выше)

    • CREATE TABLE IF NOT EXISTS `Author_By_Source_new` (`AuthorID` INTEGER NOT NULL, `SourceID` INTEGER NOT NULL, PRIMARY KEY(`AuthorID`, `SourceID`), FOREIGN KEY(`AuthorID`) REFERENCES `Authors`(`AuthorID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`SourceID`) REFERENCES `Sources`(`SourceID`) ON UPDATE NO ACTION ON DELETE NO ACTION );
    • ПРИМЕЧАНИЕ имя таблицы было изменено, то есть добавлено _new

Пример

Вот пример, основанный на том, что доступны из вашего кода.

Исходная предварительно заполненная база данных

База данных была создана с помощью средства управления SQLite (Navicat) с использованием: -

DROP TABLE IF EXISTS Author_By_Source;
DROP TABLE IF EXISTS Authors;
DROP TABLE IF EXISTS Sources;
CREATE TABLE IF NOT EXISTS Authors (AuthorID INTEGER PRIMARY KEY, AuthorName TEXT);
CREATE TABLE IF NOT EXISTS Sources (SourceID INTEGER PRIMARY KEY, SourceName TEXT);
/* Create the table so that it would cause issues in Room  */
CREATE TABLE IF NOT EXISTS Author_By_Source (
    AuthorId INTEGER, SourceId INTEGER, 
    FOREIGN KEY (AuthorID) REFERENCES Authors(AuthorID), 
    FOREIGN KEY (SourceID) REFERENCES Sources(SourceID)
);

INSERT INTO Authors (AuthorName) VALUES ('Fred'),('Mary'),('Joan'),('Bert'),('Alan');
INSERT INTO Sources (SourceName) VALUES ('S1'),('S2'),('S3'),('S4'),('S5');
INSERT INTO Author_By_Source VALUES(1,1),(2,2),(3,3),(4,4),(5,5),(1,4),(3,4),(5,2);

SELECT AuthorName, SourceName 
    FROM Author_By_Source 
        JOIN Authors ON Author_By_Source.AuthorID = Authors.AuthorID
        JOIN Sources ON Author_By_Source.SourceID = Sources.SourceID
;

После запуска Существо: -

AuthorName  SourceName
Fred        S1
Mary        S2
Joan        S3
Bert        S4
Alan        S5
Fred        S4
Joan        S4
Alan        S2

Как и ожидалось, попытка использовать это приводит к: -

java.lang.RuntimeException: Unable to start activity ComponentInfo{a.a.so59756782javaroomprepopulatedconversion/a.a.so59756782javaroomprepopulatedconversion.MainActivity}: java.lang.IllegalStateException: Pre-packaged database has an invalid schema: Authors(a.a.so59756782javaroomprepopulatedconversion.Authors).
 Expected:
TableInfo{name='Authors', columns={AuthorName=Column{name='AuthorName', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, AuthorID=Column{name='AuthorID', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=[]}
 Found:
TableInfo{name='Authors', columns={AuthorID=Column{name='AuthorID', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null'}, AuthorName=Column{name='AuthorName', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}

Преобразованию

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

BEGIN TRANSACTION;
DROP TABLE IF EXISTS Author_By_Source;
DROP TABLE IF EXISTS Authors;
DROP TABLE IF EXISTS Sources;
CREATE TABLE IF NOT EXISTS `Authors` (`AuthorID` INTEGER NOT NULL, `AuthorName` TEXT, PRIMARY KEY(`AuthorID`));
CREATE TABLE IF NOT EXISTS `Sources` (`SourceID` INTEGER NOT NULL, `SourceName` TEXT, PRIMARY KEY(`SourceID`));
/* Create the table so that it would cause issues in Room  */
CREATE TABLE IF NOT EXISTS Author_By_Source (
    AuthorId INTEGER, SourceId INTEGER, 
    FOREIGN KEY (AuthorID) REFERENCES Authors(AuthorID), 
    FOREIGN KEY (SourceID) REFERENCES Sources(SourceID)
);

INSERT INTO Authors (AuthorName) VALUES ('Fred'),('Mary'),('Joan'),('Bert'),('Alan');
INSERT INTO Sources (SourceName) VALUES ('S1'),('S2'),('S3'),('S4'),('S5');
INSERT INTO Author_By_Source VALUES(1,1),(2,2),(3,3),(4,4),(5,5),(1,4),(3,4),(5,2);

SELECT AuthorName, SourceName 
    FROM Author_By_Source 
        JOIN Authors ON Author_By_Source.AuthorID = Authors.AuthorID
        JOIN Sources ON Author_By_Source.SourceID = Sources.SourceID
;   

/* Just once */
PRAGMA foreign_keys = 0;

/* Per table */
DROP TABLE IF EXISTS Author_By_Source_new;
DROP TABLE IF EXISTS Author_By_Source_original;
CREATE TABLE IF NOT EXISTS `Author_By_Source_new` 
    (
        `AuthorID` INTEGER NOT NULL, 
        `SourceID` INTEGER NOT NULL, 
        PRIMARY KEY(`AuthorID`, `SourceID`), 
        FOREIGN KEY(`AuthorID`) REFERENCES `Authors`(`AuthorID`) ON UPDATE NO ACTION ON DELETE NO ACTION , 
        FOREIGN KEY(`SourceID`) REFERENCES `Sources`(`SourceID`) ON UPDATE NO ACTION ON DELETE NO ACTION 
    );
DELETE FROM Author_By_Source_new;
INSERT INTO Author_By_Source_new SELECT * FROM Author_By_Source;
ALTER TABLE Author_By_Source RENAME TO Author_By_Source_original;
ALTER TABLE Author_By_Source_new RENAME TO Author_By_Source;
/* IF HAPPY THEN DO */
DROP TABLE IF EXISTS Author_By_Source_original;

-- ...... do the equivalent for all tables

CREATE INDEX IF NOT EXISTS `index_Author_By_Source_SourceID` ON `Author_By_Source` (`SourceID`);
CREATE INDEX IF NOT EXISTS `index_Author_By_Source_AuthorID` ON `Author_By_Source` (`AuthorID`);

COMMIT;

/* Just once */
PRAGMA foreign_keys = 1;
PRAGMA foreign_key_check;
PRAGMA integrity_check;
SELECT sql FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite%';

SELECT AuthorName, SourceName 
    FROM Author_By_Source 
        JOIN Authors ON Author_By_Source.AuthorID = Authors.AuthorID
        JOIN Sources ON Author_By_Source.SourceID = Sources.SourceID
;

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

myDB.getOpenHelper().getWritableDatabase(); используется для принудительного немедленного открытия и, следовательно, копирования базы данных.

getAllAuthorsWithSources() делает это так говорит, используя @Query("SELECT * FROM Author_By_Source") List<AuthorWithSources> getAllAuthorsWithSources();

AuthorWithSources будучи: -

public class AuthorWithSources {

    @Embedded
    AuthorBySource authorBySource;
    @Relation(entity = Authors.class,parentColumn = "AuthorID",entityColumn = "AuthorID")
    Authors authors;
    @Relation(entity = Sources.class,parentColumn = "SourceID",entityColumn = "SourceID")
    Sources sources;
}

Результат

Нет ошибок и в журнале: -

2020-01-16 10:09:23.623 D/AUTHORSOURCEINFO: Author = FredSource = S1
2020-01-16 10:09:23.623 D/AUTHORSOURCEINFO: Author = MarySource = S2
2020-01-16 10:09:23.623 D/AUTHORSOURCEINFO: Author = JoanSource = S3
2020-01-16 10:09:23.623 D/AUTHORSOURCEINFO: Author = BertSource = S4
2020-01-16 10:09:23.623 D/AUTHORSOURCEINFO: Author = AlanSource = S5
2020-01-16 10:09:23.623 D/AUTHORSOURCEINFO: Author = FredSource = S4
2020-01-16 10:09:23.623 D/AUTHORSOURCEINFO: Author = JoanSource = S4
2020-01-16 10:09:23.623 D/AUTHORSOURCEINFO: Author = AlanSource = S2

Альтернативный подход

Существует инструмент, как приложение, который преобразует базы данных для использования в Room и генерирует код java для сущностей / Dao's.

Инструмент подробно здесь RoomExistingSQLiteDBConverter

Использование инструмента и

  • копирование сгенерированного java исходного кода в проект а затем
  • посещение каждого файла для разрешения импорта (предполагается, что правильное имя пакета было введите в противном случае пакет также должен быть скорректирован или добавлен) и затем
  • создание папки ресурсов в проекте и затем
  • копирование сгенерированной базы данных
  • и, наконец, создание кода для использования базы данных, например

    soanswersDatabase = Room.databaseBuilder(this,SoanswersDatabase.class,SoanswersDatabase.DBNAME)
            .allowMainThreadQueries()
            .createFromAsset(SoanswersDatabase.DBNAME)
            .build();
    List<Author_By_Source> authorBySourceList = soanswersDatabase.getAuthor_By_SourceDao().getEveryAuthor_By_Source();
    for (Author_By_Source abs: authorBySourceList) {
        Log.d("AUTHORSOURCEINFO",
                "AuthorReference = " + String.valueOf(abs.getAuthorId())
                + " Sourcereference = " + String.valueOf(abs.getSourceId())
        );
    }
    
  • , т. Е. Приведенный выше код является единственным введенным кодом. Результат: -
2020-01-16 10:46:27.907 D/AUTHORSOURCEINFO: AuthorReference = 1 Sourcereference = 1
2020-01-16 10:46:27.907 D/AUTHORSOURCEINFO: AuthorReference = 2 Sourcereference = 2
2020-01-16 10:46:27.907 D/AUTHORSOURCEINFO: AuthorReference = 3 Sourcereference = 3
2020-01-16 10:46:27.907 D/AUTHORSOURCEINFO: AuthorReference = 4 Sourcereference = 4
2020-01-16 10:46:27.907 D/AUTHORSOURCEINFO: AuthorReference = 5 Sourcereference = 5
2020-01-16 10:46:27.907 D/AUTHORSOURCEINFO: AuthorReference = 1 Sourcereference = 4
2020-01-16 10:46:27.907 D/AUTHORSOURCEINFO: AuthorReference = 3 Sourcereference = 4
2020-01-16 10:46:27.907 D/AUTHORSOURCEINFO: AuthorReference = 5 Sourcereference = 2
...