Ограничение FOREIGN KEY в Android Room не выполнено
Я пытаюсь спроектировать и реализовать древовидную структуру папок в Android SQLite с помощью Android Room Persistence (ORM), который Google представил в I/O 2017. В моем дизайне папка может содержать другие папки и файлы. Вот мои коды для папки и файла:
Модель файла:
@Entity(tableName = "files", foreignKeys = @ForeignKey(entity = Folder.class,
parentColumns = "id",
childColumns = "parent_id",
onDelete = CASCADE))
public class File {
@PrimaryKey(autoGenerate = true)
private int id;
private String title;
private Date creationDate;
@ColumnInfo(name = "parent_id")
public int parentId;
//here setters and getters skipped but exist in original code
}
И вот код папки:
@Entity(tableName = "folders", foreignKeys = @ForeignKey(entity = Folder.class,
parentColumns = "id",
childColumns = "parent_id",
onDelete = CASCADE,onUpdate = SET_NULL))
public class Folder {
@PrimaryKey(autoGenerate = true)
private int id;
private String name;
@ColumnInfo(name = "parent_id")
private int parentId;
}
Который имеет внешний ключ в столбце идентификатора себя для родителя.
а вот FolderDAO:
@Dao
public interface FolderDAO {
@Insert(onConflict = OnConflictStrategy.REPLACE)
public void insertFolder(Folder... folders);
@Update
public void updateFolder(Folder... folders);
@Delete
public void deleteFolders(Folder... folders);
@Query("SELECT * FROM folders")
List<Folder> getAll();
@Query("SELECT * FROM folders WHERE id IN (:folderIds)")
List<Folder> loadAllByIds(int[] folderIds);
}
Но когда я делаю объект папки и пытаюсь вставить его:
AsyncTask.execute(new Runnable() {
@Override
public void run() {
Folder folder = new Folder();
folder.setName("All");
DatabaseInstance.getInstance(getApplicationContext()).folderDAO().insertFolder(folder);
}
});
получите эту ошибку:
Ограничение FOREIGN KEY не выполнено
--------- beginning of crash
E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #1
Process: sahraei.hamidreza.com.note, PID: 1835
android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed (code 787)
at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)
at android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId(SQLiteConnection.java:782)
at android.database.sqlite.SQLiteSession.executeForLastInsertedRowId(SQLiteSession.java:788)
at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:86)
at android.arch.persistence.db.framework.FrameworkSQLiteStatement.executeInsert(FrameworkSQLiteStatement.java:80)
at android.arch.persistence.room.EntityInsertionAdapter.insert(EntityInsertionAdapter.java:80)
at sahraei.hamidreza.com.note.DAO.FolderDAO_Impl.insertFolder(FolderDAO_Impl.java:80)
at sahraei.hamidreza.com.note.ItemListActivity$1.run(ItemListActivity.java:62)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:818)
Кто-нибудь знает, что не так или предлагает другой дизайн для моего проекта таблицы?
2 ответа
Я получил это на работу, но не используя int
первичные ключи. Я не большой поклонник таких сценариев ORM, просто из-за таких проблем.
Итак, вот референс Category
класс, который использует UUID
для его первичного ключа:
/***
Copyright (c) 2017 CommonsWare, LLC
Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless required
by applicable law or agreed to in writing, software distributed under the
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
OF ANY KIND, either express or implied. See the License for the specific
language governing permissions and limitations under the License.
*/
package com.commonsware.android.room.dao;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.ForeignKey;
import android.arch.persistence.room.Ignore;
import android.arch.persistence.room.Index;
import android.arch.persistence.room.PrimaryKey;
import java.util.UUID;
import static android.arch.persistence.room.ForeignKey.CASCADE;
@Entity(
tableName="categories",
foreignKeys=@ForeignKey(
entity=Category.class,
parentColumns="id",
childColumns="parentId",
onDelete=CASCADE),
indices=@Index(value="parentId"))
public class Category {
@PrimaryKey
public final String id;
public final String title;
public final String parentId;
@Ignore
public Category(String title) {
this(title, null);
}
@Ignore
public Category(String title, String parentId) {
this(UUID.randomUUID().toString(), title, parentId);
}
public Category(String id, String title, String parentId) {
this.id=id;
this.title=title;
this.parentId=parentId;
}
}
Теперь вы можете использовать методы DAO, такие как:
@Query("SELECT * FROM categories WHERE parentId IS NULL")
Category findRootCategory();
@Query("SELECT * FROM categories WHERE parentId=:parentId")
List<Category> findChildCategories(String parentId);
Мне понадобилось много времени, чтобы понять, что parentColumns
элемент, который является автоматически сгенерированным первичным ключом, вообще не разрешен, даже если это uuid, использующий класс данных Kotlin со значением по умолчанию, например:
MyClass{
//Don't do this
@PrimaryKey(autoGenerate = true)
var uuid: String = UUID.randomUUID()
}
Всегда убедитесь, что родительский столбец указан непосредственно вами.