Android Room: Fügen Sie Relation Entities mit Room ein

89

Ich habe eine zu vielen Beziehungen in Room mithilfe von Relation hinzugefügt . Ich habe auf diesen Beitrag verwiesen, um den folgenden Code für die Beziehung in Raum zu schreiben.

Der Beitrag erklärt, wie die Werte aus der Datenbank gelesen werden, aber das Speichern der Entitäten in der Datenbank führte userIddazu, dass sie leer waren, was bedeutet, dass zwischen den beiden Tabellen keine Beziehung besteht.

Ich bin mir nicht sicher, was der ideale Weg zu inserteiner Userund List of Petin die Datenbank ist, während ich userIdWert habe.

1) Benutzerentität:

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

2) Haustierentität:

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

Um die Datensätze aus der Datenbank abzurufen, verwenden wir Folgendes DAO:

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

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

BEARBEITEN

Ich habe dieses Problem https://issuetracker.google.com/issues/62848977 im Issue-Tracker erstellt. Hoffentlich werden sie etwas dagegen unternehmen.

Akshay Chordiya
quelle
Sie sagen also, dass "Beziehungen" bei der Aktualisierung von 2.2 Priorität haben. Aktuelle Version ist "Raum 2.1.0-alpha03" vom 4. Dezember 2018.
Lech Osiński
Ja, lesen Sie einfach ihren Kommentar im Issue-Tracker. Es wird
einige

Antworten:

44

Sie können dies tun, indem Sie Ihr Dao von einer Schnittstelle in eine abstrakte Klasse ändern.

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

Sie können auch weiter gehen, indem Sie ein UserObjekt haben@Ignored List<Pet> pets

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

    @Ignored
    public List<Pet> pets
}

und dann kann das Dao UserWithPetsdem Benutzer zugeordnet werden:

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

Damit haben Sie das volle Dao:

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

Vielleicht möchten Sie die Methoden insertAll(List<Pet>)und insertPetsForUser(User, List<Pet>)stattdessen in einem PetDAO haben ... wie Sie Ihre DAOs partitionieren, liegt bei Ihnen! :) :)

Auf jeden Fall ist es nur eine andere Option. Das Umschließen Ihrer DAOs in DataSource-Objekte funktioniert ebenfalls.

Kieran Macdonald-Hall
quelle
Tolle Idee, die Convenience-Methode in eine abstrakte Klasse anstatt in eine Schnittstelle zu setzen.
Akshay Chordiya
7
Wenn wir die Pet-bezogenen Methoden in das PetDao verschieben würden, wie würden wir auf die PetDao-Methoden im UserDao verweisen?
Sbearben
4
Danke für deine Antwort. Aber wie setzt pet.setUserId (user.getId ()) in der Methode insertPetsForUser (Benutzer user, List <Pet> Haustiere) die userId? Nehmen wir an, Sie erhalten das Benutzerobjekt von einer API und zu diesem Zeitpunkt hat es keine ID (primaryKey), da es nicht in RoomDb gespeichert wurde. Wenn Sie also user.getId () aufrufen, erhalten Sie jeweils nur den Standardwert 0 Benutzer, da es noch gespeichert werden muss. Wie gehen Sie damit um, weil user.getId () für jeden Benutzer immer 0 zurückgibt. Vielen Dank
Ikhiloya Imokhai
38

Bis zu einem Update in der Raumbibliothek gibt es keine native Lösung, aber Sie können dies mit einem Trick tun. Finden Sie unten erwähnt.

  1. Erstellen Sie einfach einen Benutzer mit Haustieren (Haustiere ignorieren). Getter und Setter hinzufügen. Beachten Sie, dass wir unsere IDs später manuell einstellen müssen und nicht verwenden können autogenerate.

    @Entity
    public class User {
          @PrimaryKey
          public int id; 
    
          @Ignore
          private List<Pet> petList;
    }
    
  2. Erstellen Sie ein Haustier.

    @Entity 
    public class Pet 
    {
        @PrimaryKey
        public int id;     
        public int userId; 
        public String name;
    }
    
  3. Das UserDao sollte eine abstrakte Klasse anstelle einer Schnittstelle sein. Dann endlich in deinem 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;
    }
    

Ihr Problem kann dadurch gelöst werden, ohne UserWithPets POJO zu erstellen.

Dipendra Sharma
quelle
2
Ich mag dieses, weil es das UserWithPets POJO vermeidet. Bei Verwendung von Standardmethoden kann das DAO auch eine Schnittstelle sein. Der einzige Nachteil, den ich sehe, ist, dass insertUser () und insertPetList () öffentliche Methoden sind, aber nicht von einem Client verwendet werden sollten. Ich habe den Namen dieser Methoden mit einem Unterstrich versehen, um zu zeigen, dass sie nicht wie oben gezeigt verwendet werden sollten.
Guglhupf
1
Kann jemand zeigen, wie man dies in einer Aktivität richtig implementiert? Richtig, ich weiß nicht, wie ich die IDs richtig generieren soll.
Philipp
@Philipp Ich beschäftige mich mit dem Generieren der IDs. Hast du eine Lösung gefunden?
Sochas
1
Vielen Dank für Ihre Antwort @Philipp, ich habe es auf die gleiche Weise
getan
2
Danke @Philipp Ich mag deine Antwort für ihre Flexibilität! Bei der automatischen Generierung von IDs rufe ich in meinem Fall insertUser()zuerst auf, um eine automatische Generierung zu erhalten userId, und ordne diese dann userIddem Feld userId in der Pet-Klasse zu insertPet().
Robert
11

Da Room die Beziehungen der Entitäten nicht verwaltet, müssen Sie die userIdfür jedes Haustier selbst festlegen und speichern. Solange es nicht zu viele Haustiere gleichzeitig gibt, würde ich eine insertAllMethode verwenden, um es kurz zu halten.

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

Ich glaube nicht, dass es im Moment einen besseren Weg gibt.

Um die Handhabung zu vereinfachen, würde ich eine Abstraktion in der Ebene über den DAOs verwenden:

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

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

    petDao.insertAll(pets);
}
tknell
quelle
Ich habe das gleiche mit versucht Stream. Ich hoffe nur, dass es einen besseren Weg gibt, dies zu tun.
Akshay Chordiya
Ich glaube nicht, dass es einen besseren Weg gibt, da die Dokumentation besagt, dass sie absichtlich Referenzen aus der Bibliothek herausgelassen haben: developer.android.com/topic/libraries/architecture/…
tknell
Ich habe dieses Problem issuetracker.google.com/issues/62848977 im Issue-Tracker erstellt. Hoffentlich werden sie etwas dagegen unternehmen.
Akshay Chordiya
1
Nicht wirklich eine native Lösung, aber Sie müssen keine Schnittstellen für DAOs verwenden, Sie können auch abstrakte Klassen verwenden ... was bedeutet, dass die Convenience-Methoden in DAO selbst gespeichert werden können, wenn Sie keine Wrapper-Klasse haben möchten. Siehe meine Antwort für weitere Informationen ...
Kieran Macdonald-Hall
6

Derzeit gibt es keine native Lösung für dieses Problem. Ich habe diese https://issuetracker.google.com/issues/62848977 im Issue-Tracker von Google erstellt, und das Architecture Components Team hat angekündigt, eine native Lösung in oder nach Version 1.0 der Room-Bibliothek hinzuzufügen.

Temporäre Problemumgehung:

In der Zwischenzeit können Sie die von tknell erwähnte Lösung verwenden .

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

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

    petDao.insertAll(pets);
}
Akshay Chordiya
quelle
Wenn ich mir andere Lösungen anschaue, sehe ich, dass eine abstrakte Klasse anstelle einer Schnittstelle verwendet wird, während das Dao erstellt und ähnliche konkrete Methoden als Teil dieser abstrakten Klassen hinzugefügt werden. Aber ich kann immer noch nicht verstehen, wie man eine Child-Dao-Instanz in diese Methoden einbindet? z.B. Wie können Sie die petDao-Instanz in userDao abrufen?
Purush Pawar
5

Ab Version 2.1.0 scheint Room nicht für Modelle mit verschachtelten Beziehungen geeignet zu sein. Es brauchte viel Boilerplate-Code, um sie zu warten. ZB manuelles Einfügen von Listen, Erstellen und Zuordnen lokaler IDs.

Diese Relation-Mapping-Vorgänge werden von Requery https://github.com/requery/requery ausgeführt. Außerdem gibt es keine Probleme beim Einfügen von Enums und einige Konverter für andere komplexe Typen wie URI.

Hauptaktivität
quelle
2

Ich habe es geschafft, es mit einer relativ einfachen Problemumgehung richtig einzufügen. Hier sind meine Entitäten:

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

Ich verwende autoGenerate für den Wert der automatischen Inkrementierung (long wird mit einem Zweck verwendet). Hier ist meine Lösung:

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

Ich hatte ein Problem beim Verknüpfen der Entitäten. Die automatische Generierung erzeugte ständig die "Rezept-ID = 0". Das Einfügen der Rezept-Entität hat es zunächst für mich behoben.

stefan.georgiev.uzunov
quelle