Stanza Android: inserisci le entità di relazione utilizzando Stanza


90

Ho aggiunto una relazione a molte in Room utilizzando Relation . Ho fatto riferimento a questo post per scrivere il seguente codice per la relazione in Room.

Il post spiega come leggere i valori dal database, ma la memorizzazione delle entità nel database è risultata userIdvuota, il che significa che non esiste alcuna relazione tra le 2 tabelle.

Non sono sicuro di quello che è il modo ideale per insertun Usere List of Petnella banca dati, pur avendo userIdvalore.

1) Entità utente:

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

2) Pet Entity:

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

Ora per recuperare i record dal DB usiamo quanto segue DAO:

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

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

MODIFICARE

Ho creato questo problema https://issuetracker.google.com/issues/62848977 nel tracker dei problemi. Si spera che faranno qualcosa al riguardo.


Quindi dicono che le "relazioni" sono la priorità nell'aggiornamento 2.2. La versione attuale è "Room 2.1.0-alpha03" del 4 dicembre 2018.
Lech Osiński,

Sì, basta leggere il loro commento nel tracker del problema. Ci vorrà del tempo, nel frattempo puoi usare le soluzioni alternative
Akshay Chordiya

Risposte:


44

Puoi farlo cambiando il tuo Dao da un'interfaccia a una classe astratta.

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

Puoi anche andare oltre facendo in modo che un Useroggetto abbia un'estensione@Ignored List<Pet> pets

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

    @Ignored
    public List<Pet> pets
}

e poi il Dao può mappare UserWithPetsall'Utente:

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

Questo ti lascia con il Dao completo:

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

Potresti invece voler avere i metodi insertAll(List<Pet>)e insertPetsForUser(User, List<Pet>)in un PetDAO ... come partizionare i tuoi DAO dipende da te! :)

Comunque, è solo un'altra opzione. Funziona anche il wrapping dei DAO negli oggetti DataSource.


Ottima idea quella di mettere il metodo di convenienza nella classe astratta invece che nell'interfaccia.
Akshay Chordiya

7
se dovessimo spostare i metodi relativi a Pet in PetDao, come faremmo riferimento ai metodi PetDao in UserDao?
sbearben

4
grazie per la tua risposta. Ma come fa pet.setUserId (user.getId ()) nel metodo insertPetsForUser (User user, List <Pet> pets) a impostare userId? supponiamo che tu stia ottenendo l'oggetto utente da un'API ea questo punto non ha id (primaryKey) poiché non è stato salvato in RoomDb, quindi la chiamata di user.getId () produrrà solo un valore predefinito di 0, per ogni utente in quanto deve ancora essere salvato. Come gestisci tale perché user.getId () restituisce sempre 0 per ogni utente. Grazie
Ikhiloya Imokhai

40

Non esiste una soluzione nativa fino a qualsiasi aggiornamento in Room Library, ma puoi farlo con un trucco. Trova di seguito menzionato.

  1. Basta creare un utente con animali domestici (ignorare gli animali domestici). Aggiungi getter e setter. Si noti che dobbiamo impostare manualmente i nostri ID in un secondo momento e non possiamo utilizzarli autogenerate.

    @Entity
    public class User {
          @PrimaryKey
          public int id; 
    
          @Ignore
          private List<Pet> petList;
    }
    
  2. Crea un animale domestico.

    @Entity 
    public class Pet 
    {
        @PrimaryKey
        public int id;     
        public int userId; 
        public String name;
    }
    
  3. UserDao dovrebbe essere una classe astratta invece di un'interfaccia. Poi finalmente nel tuo 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;
    }
    

Il tuo problema può essere risolto senza creare POJO UserWithPets.


2
Mi piace questo, perché evita il POJO di UserWithPets. Utilizzando i metodi predefiniti, DAO può essere anche un'interfaccia. L'unico aspetto negativo che vedo è che insertUser () e insertPetList () sono metodi pubblici, ma non dovrebbero essere usati da un client. Ho messo un carattere di sottolineatura prima del nome di questi metodi per mostrare che non dovrebbero essere usati, come mostrato sopra.
guglhupf

1
Qualcuno può mostrare come implementarlo correttamente in un'attività? So bene che non so come generare correttamente gli ID.
Philipp

@Philipp mi sto occupando di come generare gli id, hai trovato una soluzione?
sochas

1
Grazie per la tua risposta @Philipp, l'ho fatto allo stesso modo :)
sochas il

2
Grazie @Philipp mi piace la tua risposta per la sua flessibilità! Per gli ID di generazione automatica, nel mio caso chiamo insertUser()prima per ottenere la generazione automatica userId, quindi lo assegno userIdal campo userId nella classe Pet, quindi ciclo a insertPet().
Robert

11

Poiché Room non gestisce le relazioni delle entità, devi impostare userIdtu stesso il su ogni animale e salvarlo. Finché non ci sono troppi animali domestici contemporaneamente, userei un insertAllmetodo per mantenerlo breve.

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

Non credo ci sia modo migliore al momento.

Per semplificare la gestione, utilizzerei un'astrazione nel livello sopra i DAO:

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

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

    petDao.insertAll(pets);
}

Ho provato lo stesso utilizzo Stream. Spero solo che ci sia un modo migliore per farlo.
Akshay Chordiya

Non penso che ci sia un modo migliore, poiché la documentazione dice che hanno intenzionalmente lasciato i riferimenti fuori dalla libreria: developer.android.com/topic/libraries/architecture/…
tknell

Ho creato questo problema issuetracker.google.com/issues/62848977 sul tracker dei problemi. Si spera che faranno qualcosa al riguardo.
Akshay Chordiya

1
Non è proprio una soluzione nativa, ma non è necessario utilizzare interfacce per DAO, è possibile utilizzare anche classi astratte ... il che significa che i metodi di convenienza possono trovarsi all'interno di DAO stesso se non si desidera avere una classe wrapper. Vedi la mia risposta per maggiori informazioni ...
Kieran Macdonald-Hall

6

Attualmente non esiste una soluzione nativa a questo problema. Ho creato questo https://issuetracker.google.com/issues/62848977 sul tracker dei problemi di Google e il team di Architecture Components ha affermato che aggiungerà una soluzione nativa nella o dopo la v1.0 della libreria Room.

Soluzione temporanea:

Nel frattempo puoi usare la soluzione menzionata da tknell .

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

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

    petDao.insertAll(pets);
}

Guardando altre soluzioni, vedo che il modo per farlo è usare una classe astratta invece di un'interfaccia durante la creazione del dao e l'aggiunta di metodi concreti simili parte di quelle classi astratte. Ma non riesco ancora a capire come ottenere un'istanza dao figlio all'interno di questi metodi? per esempio. come riesci a ottenere l'istanza di petDao all'interno di userDao?
Purush Pawar

5

Ora alla v2.1.0 Room sembra non essere adatto a modelli con relazioni annidate. Aveva bisogno di un sacco di codice boilerplate per mantenerli. Ad esempio, inserimento manuale di elenchi, creazione e mappatura di ID locali.

Queste operazioni di mappatura delle relazioni vengono eseguite immediatamente da Requery https://github.com/requery/requery Inoltre non ha problemi con l'inserimento di Enum e ha alcuni convertitori per altri tipi complessi come URI.


2

Sono riuscito a inserirlo correttamente con una soluzione relativamente semplice. Ecco le mie entità:

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

Sto usando autoGenerate per il valore di incremento automatico (long è usato con scopi). Ecco la mia soluzione:

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

Ho avuto problemi a collegare le entità, generava automaticamente "ricettaId = 0" prodotta tutto il tempo. L'inserimento dell'entità ricetta in primo luogo l'ha risolto per me.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.