Android Room FOREIGN KEY约束失败

Ham*_*aei 14 sqlite orm android android-orm android-room

我试图在Android SQLite中设计和实现文件夹树结构,借助Google在I/O 2017中引入的Android Room Persistence(一种ORM).在我的设计中,一个文件夹可以包含另一个文件夹和文件.这是我的文件夹和文件的代码:

文件模型:

@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
}
Run Code Online (Sandbox Code Playgroud)

这是文件夹代码:

@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;
}
Run Code Online (Sandbox Code Playgroud)

父ID的ID列上有一个外键.

这里是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);
}
Run Code Online (Sandbox Code Playgroud)

但是当我创建一个文件夹对象并尝试插入它时:

AsyncTask.execute(new Runnable() {
        @Override
        public void run() {
            Folder folder = new Folder();
            folder.setName("All");

            DatabaseInstance.getInstance(getApplicationContext()).folderDAO().insertFolder(folder);

        }
    });
Run Code Online (Sandbox Code Playgroud)

得到这个错误:

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)
Run Code Online (Sandbox Code Playgroud)

有谁知道什么是错的或建议我的项目表的另一个设计?

Com*_*are 10

我让这个工作,但不使用int主键.我不是那种ORM场景的忠实粉丝,只是因为这些问题.

所以,这是一个自引用Category类,它使用a 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;
  }
}
Run Code Online (Sandbox Code Playgroud)

现在你可以使用DAO方法:

@Query("SELECT * FROM categories WHERE parentId IS NULL")
Category findRootCategory();

@Query("SELECT * FROM categories WHERE parentId=:parentId")
List<Category> findChildCategories(String parentId);
Run Code Online (Sandbox Code Playgroud)

  • @HamidrezaSahraei:唯一重要的区别是你使用自动生成的`int`键,而我不是.您可以尝试切换到`Integer`并查看`null`默认值是否更好.我的猜测是`0`默认`int`打破了外键约束,因为Room无法判断'0`表示"无关系".但是,这只是猜测. (3认同)

Dan*_*son 5

我花了很长时间才弄清楚,parentColumns似乎根本不允许使用作为自动生成的主键的元素,即使它是使用带有默认值的Kotlin数据类的uuid,也是如此:

MyClass{
    //Don't do this
    @PrimaryKey(autoGenerate = true)
    var uuid: String = UUID.randomUUID()
}
Run Code Online (Sandbox Code Playgroud)

始终确保您直接指定父列。

  • 看来,当您使用`autoGenerate = true`时,房间使用`rowId`列作为主键。但是sqlite不允许在rowId上使用FOREIGN KEY。FOREIGN KEY必须以命名列为目标。https://www.sqlite.org/foreignkeys.html这将解释为什么您不能使用以自动生成的列为目标的外键。 (2认同)