web-dev-qa-db-fra.com

Android Room La contrainte FOREIGN KEY a échoué

J'essaie de concevoir et d'implémenter une arborescence de dossiers dans Android SQLite avec l'aide de Android Room Persistence (un ORM) que Google a introduit dans les E/S 2017. Dans ma conception, un dossier peut contenir d'autres dossiers et fichiers. Voici mes codes pour dossier et fichier:

Modèle de fichier:

@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
}

Et voici le code du dossier:

@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;
}

Qui ont une clé étrangère dans la colonne ID d'elle-même pour le parent.

et voici 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);
}

Mais quand je crée un objet dossier et essaie de l'insérer:

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

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

        }
    });

obtenir cette erreur:

Échec de la contrainte 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)

Est-ce que quelqu'un sait ce qui ne va pas ou suggère une autre conception pour ma table de projet?

13
Hamidreza Sahraei

J'ai réussi à le faire fonctionner, mais sans utiliser les clés primaires int. Je ne suis pas un grand fan de ceux-ci pour ce type de scénario ORM, juste à cause de ce genre de problèmes.

Voici donc une classe Category auto-référentielle qui utilise un UUID pour sa clé primaire:

/***
 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;
  }
}

Vous pouvez maintenant avoir des méthodes DAO comme:

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

@Query("SELECT * FROM categories WHERE parentId=:parentId")
List<Category> findChildCategories(String parentId);
9
CommonsWare

Il m'a fallu beaucoup de temps pour comprendre que le fait d'avoir un élément parentColumns qui est une clé primaire générée automatiquement ne semble pas du tout être autorisé, même s'il s'agit d'un uuid utilisant une classe de données Kotlin avec une valeur par défaut comme cette:

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

Assurez-vous toujours que la colonne parent est spécifiée directement par vous.

5
Daniel Wilson