Android Room: insérer des entités de relation à l'aide de Room

88

J'ai ajouté une à plusieurs relations dans Room en utilisant Relation . J'ai fait référence à ce post pour écrire le code suivant pour la relation dans la salle.

Le message explique comment lire les valeurs de la base de données, mais le stockage des entités dans la base de données a abouti userIdà être vide, ce qui signifie qu'il n'y a pas de relation entre les 2 tables.

Je ne sais pas quelle est la manière idéale à insertune Useret List of Petdans la base de données tout en ayant la userIdvaleur.

1) Entité utilisateur:

@Entity
public class User {
    @PrimaryKey
    public int id; // User id
}

2) Entité pour animaux de compagnie:

@Entity
public class Pet {
    @PrimaryKey
    public int id;     // Pet id
    public int userId; // User id
    public String name;
}

3) UserWithPets POJO:

// Note: No annotation required at this class definition.
public class UserWithPets {
   @Embedded
   public User user;

   @Relation(parentColumn = "id", entityColumn = "userId", entity = Pet.class)
   public List<Pet> pets;
}

Maintenant, pour récupérer les enregistrements de DB, nous utilisons ce qui suit DAO:

@Dao
public interface UserDao {
    @Insert
    fun insertUser(user: User)

    @Query("SELECT * FROM User")
    public List<UserWithPets> loadUsersWithPets();
}

ÉDITER

J'ai créé ce problème https://issuetracker.google.com/issues/62848977 sur l'outil de suivi des problèmes. J'espère qu'ils feront quelque chose à ce sujet.

Akshay Chordiya
la source
Donc, ils disent que les «relations» sont la priorité dans la mise à jour 2.2. La version actuelle est "Room 2.1.0-alpha03" du 4 décembre 2018.
Lech Osiński
Ouais, lisez simplement leur commentaire dans le suivi des problèmes. Cela va prendre du temps, en attendant, vous pouvez utiliser les solutions de contournement
Akshay Chordiya

Réponses:

43

Vous pouvez le faire en changeant votre Dao d'une interface à une classe abstraite.

@Dao
public abstract class UserDao {

    public void insertPetsForUser(User user, List<Pet> pets){

        for(Pet pet : pets){
            pet.setUserId(user.getId());
        }

        _insertAll(pets);
    }

    @Insert
    abstract void _insertAll(List<Pet> pets);  //this could go in a PetDao instead...

    @Insert
    public abstract void insertUser(User user);

    @Query("SELECT * FROM User")
    abstract List<UserWithPets> loadUsersWithPets();
}

Vous pouvez également aller plus loin en faisant avoir un Userobjet@Ignored List<Pet> pets

@Entity
public class User {
    @PrimaryKey
    public int id; // User id

    @Ignored
    public List<Pet> pets
}

puis le Dao peut mapper UserWithPetsà l'utilisateur:

public List<User> getUsers() {
    List<UserWithPets> usersWithPets = loadUserWithPets();
    List<User> users = new ArrayList<User>(usersWithPets.size())
    for(UserWithPets userWithPets: usersWithPets) {
        userWithPets.user.pets = userWithPets.pets;
        users.add(userWithPets.user);
    }
    return users;
}

Cela vous laisse avec le Dao complet:

@Dao
public abstract class UserDao {

    public void insertAll(List<User> users) {
        for(User user:users) {
           if(user.pets != null) {
               insertPetsForUser(user, user.pets);
           }
        }
        _insertAll(users);
    }

    private void insertPetsForUser(User user, List<Pet> pets){

        for(Pet pet : pets){
            pet.setUserId(user.getId());
        }

        _insertAll(pets);
    }

    public List<User> getUsersWithPetsEagerlyLoaded() {
        List<UserWithPets> usersWithPets = _loadUsersWithPets();
        List<User> users = new ArrayList<User>(usersWithPets.size())
        for(UserWithPets userWithPets: usersWithPets) {
            userWithPets.user.pets = userWithPets.pets;
            users.add(userWithPets.user);
        }
        return users;
    }


    //package private methods so that wrapper methods are used, Room allows for this, but not private methods, hence the underscores to put people off using them :)
    @Insert
    abstract void _insertAll(List<Pet> pets);

    @Insert
    abstract void _insertAll(List<User> users);

    @Query("SELECT * FROM User")
    abstract List<UserWithPets> _loadUsersWithPets();
}

Vous voudrez peut-être avoir les méthodes insertAll(List<Pet>)et insertPetsForUser(User, List<Pet>)dans un PetDAO à la place ... la façon dont vous partitionnez vos DAO dépend de vous! :)

Quoi qu'il en soit, c'est juste une autre option. L'emballage de vos DAO dans des objets DataSource fonctionne également.

Kieran Macdonald-Hall
la source
Excellente idée de mettre la méthode de commodité dans une classe abstraite au lieu de l'interface.
Akshay Chordiya
7
si nous devions déplacer les méthodes liées à Pet dans PetDao, comment référencerions-nous les méthodes PetDao dans UserDao?
sbearben
4
Merci pour votre réponse. Mais comment pet.setUserId (user.getId ()) dans insertPetsForUser (User user, List <Pet> pets) définit-il le userId? supposons que vous récupérez l'objet utilisateur d'une API et qu'à ce stade, il n'a pas d'id (primaryKey) car il n'a pas été enregistré dans RoomDb, donc appeler user.getId () ne donnera qu'une valeur par défaut de 0, pour chaque utilisateur car il n’a pas encore été enregistré. Comment gérez-vous cela car user.getId () renvoie toujours 0 pour chaque utilisateur. Merci
Ikhiloya Imokhai
38

Il n'y a pas de solution native jusqu'à une mise à jour dans la bibliothèque de la salle, mais vous pouvez le faire par une astuce. Trouvez ci-dessous mentionné.

  1. Créez simplement un utilisateur avec des animaux domestiques (ignorez les animaux domestiques). Ajoutez un getter et un setter. Notez que nous devons définir nos identifiants manuellement plus tard et que nous ne pouvons pas utiliser autogenerate.

    @Entity
    public class User {
          @PrimaryKey
          public int id; 
    
          @Ignore
          private List<Pet> petList;
    }
    
  2. Créez un animal de compagnie.

    @Entity 
    public class Pet 
    {
        @PrimaryKey
        public int id;     
        public int userId; 
        public String name;
    }
    
  3. UserDao doit être une classe abstraite au lieu d'une interface. Puis enfin dans votre UserDao.

    @Insert
    public abstract void insertUser(User user);
    
    @Insert
    public abstract void insertPetList(List<Pet> pets);
    
    @Query("SELECT * FROM User WHERE id =:id")
    public abstract User getUser(int id);
    
    @Query("SELECT * FROM Pet WHERE userId =:userId")
    public abstract List<Pet> getPetList(int userId);
    
    public void insertUserWithPet(User user) {
        List<Pet> pets = user.getPetList();
        for (int i = 0; i < pets.size(); i++) {
            pets.get(i).setUserId(user.getId());
        }
        insertPetList(pets);
        insertUser(user);
    }
    
    public User getUserWithPets(int id) {
        User user = getUser(id);
        List<Pet> pets = getPetList(id);
        user.setPetList(pets);
        return user;
    }
    

Votre problème peut être résolu par cela sans créer UserWithPets POJO.

Dipendra Sharma
la source
2
J'aime celui-ci, car il évite le POJO UserWithPets. En utilisant les méthodes par défaut, le DAO peut également être une interface. Le seul inconvénient que je vois est que insertUser () et insertPetList () sont des méthodes publiques, mais ne devraient pas être utilisées par un client. J'ai mis un trait de soulignement avant le nom de ces méthodes pour montrer qu'elles ne doivent pas être utilisées, comme indiqué ci-dessus.
guglhupf
1
Quelqu'un peut-il montrer comment mettre en œuvre correctement cela dans une activité? Je sais bien que je ne sais pas comment générer correctement les identifiants.
Philipp
@Philipp Je m'occupe de la manière de générer les identifiants, avez-vous trouvé une solution?
sochas
1
Merci pour votre réponse @Philipp, je l'ai fait de la même manière :)
sochas
2
Merci @Philipp j'aime ta réponse pour sa flexibilité! Pour la génération automatique des identifiants, dans mon cas, j'appelle d' insertUser()abord pour obtenir une génération automatique userId, puis je l' assigne userIdau champ userId de la classe Pet, puis en boucle insertPet().
Robert le
11

Comme Room ne gère pas les relations des entités, vous devez définir vous-même les userIdsur chaque animal et les enregistrer. Tant qu'il n'y a pas trop d'animaux à la fois, j'utiliserais une insertAllméthode pour faire court.

@Dao
public interface PetDao {
    @Insert
    void insertAll(List<Pet> pets);
}

Je ne pense pas qu'il y ait de meilleur moyen pour le moment.

Pour faciliter la manipulation, j'utiliserais une abstraction dans la couche au-dessus des DAO:

public void insertPetsForUser(User user, List<Pet> pets){

    for(Pet pet : pets){
        pet.setUserId(user.getId());
    }

    petDao.insertAll(pets);
}
tknell
la source
J'ai essayé la même utilisation Stream. J'espère juste qu'il y a une meilleure façon de le faire.
Akshay Chordiya
Je ne pense pas qu'il existe un meilleur moyen, car la documentation dit qu'ils ont intentionnellement laissé des références hors de la bibliothèque: developer.android.com/topic/libraries/architecture
...
J'ai créé ce problème issuetracker.google.com/issues/62848977 sur le suivi des problèmes. J'espère qu'ils feront quelque chose à ce sujet.
Akshay Chordiya
1
Ce n'est pas vraiment une solution native, mais vous n'avez pas besoin d'utiliser d'interfaces pour les DAO, vous pouvez également utiliser des classes abstraites ... ce qui signifie que les méthodes pratiques peuvent se trouver dans DAO lui-même si vous ne voulez pas avoir de classe wrapper. Voir ma réponse pour plus d'informations ...
Kieran Macdonald-Hall
6

Il n'existe actuellement aucune solution native à ce problème. J'ai créé ce https://issuetracker.google.com/issues/62848977 sur le suivi des problèmes de Google et l'équipe des composants d'architecture a déclaré qu'elle ajouterait une solution native dans ou après la v1.0 de la bibliothèque Room.

Solution temporaire:

En attendant, vous pouvez utiliser la solution mentionnée par tknell .

public void insertPetsForUser(User user, List<Pet> pets){

    for(Pet pet : pets){
        pet.setUserId(user.getId());
    }

    petDao.insertAll(pets);
}
Akshay Chordiya
la source
En regardant d'autres solutions, je vois que la manière de procéder consiste à utiliser une classe abstraite au lieu d'une interface tout en créant le DAO et en ajoutant des méthodes concrètes similaires faisant partie de ces classes abstraites. Mais, je ne peux toujours pas comprendre comment obtenir une instance enfant dao dans ces méthodes? par exemple. comment pouvez-vous obtenir une instance petDao dans userDao?
Purush Pawar
5

Maintenant, dans la v2.1.0, Room ne semble pas convenir aux modèles avec des relations imbriquées. Il fallait beaucoup de code standard pour les maintenir. Par exemple, insertion manuelle de listes, création et mappage d'identifiants locaux.

Ces opérations de mappage de relations sont effectuées hors de la boîte par Requery https://github.com/requery/requery En outre, il n'a pas de problèmes d'insertion d'énumérations et dispose de convertisseurs pour d'autres types complexes comme URI.

Activité principale
la source
2

J'ai réussi à l'insérer correctement avec une solution de contournement relativement simple. Voici mes entités:

   @Entity
public class Recipe {
    @PrimaryKey(autoGenerate = true)
    public long id;
    public String name;
    public String description;
    public String imageUrl;
    public int addedOn;
   }


  @Entity
public class Ingredient {
   @PrimaryKey(autoGenerate = true)
   public long id;
   public long recipeId; 
   public String name;
   public String quantity;
  }

public class RecipeWithIngredients {
   @Embedded
   public  Recipe recipe;
   @Relation(parentColumn = "id",entityColumn = "recipeId",entity = Ingredient.class)
   public List<Ingredient> ingredients;

J'utilise autoGenerate pour la valeur d'auto-incrémentation (long est utilisé avec un purpoes). Voici ma solution:

  @Dao
public abstract class RecipeDao {

  public  void insert(RecipeWithIngredients recipeWithIngredients){
    long id=insertRecipe(recipeWithIngredients.getRecipe());
    recipeWithIngredients.getIngredients().forEach(i->i.setRecipeId(id));
    insertAll(recipeWithIngredients.getIngredients());
  }

public void delete(RecipeWithIngredients recipeWithIngredients){
    delete(recipeWithIngredients.getRecipe(),recipeWithIngredients.getIngredients());
  }

 @Insert
 abstract  void insertAll(List<Ingredient> ingredients);
 @Insert
 abstract long insertRecipe(Recipe recipe); //return type is the key here.

 @Transaction
 @Delete
 abstract void delete(Recipe recipe,List<Ingredient> ingredients);

 @Transaction
 @Query("SELECT * FROM Recipe")
 public abstract List<RecipeWithIngredients> loadAll();
 }

J'ai eu des problèmes pour relier les entités, générer automatiquement "recetteId = 0" produit tout le temps. L'insertion de l'entité de recette l'a d'abord corrigée pour moi.

stefan.georgiev.uzunov
la source