ห้อง Android: แทรกเอนทิตีความสัมพันธ์โดยใช้ห้อง


90

ฉันได้เพิ่มอีกหนึ่งความสัมพันธ์จำนวนมากในห้องพักโดยใช้ความสัมพันธ์ ฉันอ้างถึงโพสต์นี้เพื่อเขียนโค้ดต่อไปนี้สำหรับความสัมพันธ์ในห้อง

โพสต์จะบอกวิธีอ่านค่าจากฐานข้อมูล แต่การจัดเก็บเอนทิตีลงในฐานข้อมูลทำให้userIdว่างซึ่งหมายความว่าไม่มีความสัมพันธ์ระหว่าง 2 ตาราง

ฉันไม่แน่ใจว่าอะไรคือวิธีที่ดีที่สุดในinsertการเข้าUserและList of Petเข้าสู่ฐานข้อมูลในขณะที่มีuserIdค่า

1) เอนทิตีผู้ใช้:

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

2) หน่วยงานสัตว์เลี้ยง:

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

ตอนนี้ในการดึงข้อมูลจาก DB เราใช้สิ่งต่อไปนี้DAO:

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

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

แก้ไข

ฉันได้สร้างปัญหานี้https://issuetracker.google.com/issues/62848977บนเครื่องมือติดตามปัญหา หวังว่าพวกเขาจะทำบางอย่างเกี่ยวกับเรื่องนี้


ดังนั้นพวกเขาจึงกล่าวว่า "ความสัมพันธ์" มีความสำคัญในการอัปเดต 2.2 เวอร์ชันปัจจุบันคือ "Room 2.1.0-alpha03" ตั้งแต่วันที่ 4 ธันวาคม 2018
Lech Osiński

ใช่เพียงอ่านความคิดเห็นของพวกเขาในเครื่องมือติดตามปัญหา จะต้องใช้เวลาในขณะเดียวกันคุณสามารถใช้วิธีแก้ปัญหาได้
Akshay Chordiya

คำตอบ:


44

คุณสามารถทำได้โดยเปลี่ยน Dao ของคุณจากอินเทอร์เฟซเป็นคลาสนามธรรม

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

นอกจากนี้คุณยังสามารถไปได้ไกลขึ้นโดยให้Userวัตถุมีไฟล์@Ignored List<Pet> pets

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

    @Ignored
    public List<Pet> pets
}

จากนั้น Dao สามารถแมปUserWithPetsกับผู้ใช้:

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

สิ่งนี้ทำให้คุณเต็มไปด้วย 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();
}

คุณอาจต้องการมีinsertAll(List<Pet>)และinsertPetsForUser(User, List<Pet>)วิธีการใน PetDAO แทน ... การแบ่งพาร์ติชัน DAO ของคุณขึ้นอยู่กับคุณอย่างไร! :)

อย่างไรก็ตามมันเป็นเพียงอีกทางเลือกหนึ่ง การห่อ DAO ของคุณในออบเจ็กต์ DataSource ก็ใช้ได้เช่นกัน


ความคิดที่ดีในการใส่วิธีอำนวยความสะดวกในคลาสนามธรรมแทนอินเทอร์เฟซ
Akshay Chordiya

8
หากเราจะย้ายวิธีการที่เกี่ยวข้องกับสัตว์เลี้ยงไปยัง PetDao เราจะอ้างอิงวิธีการของ PetDao ภายใน UserDao ได้อย่างไร?
sbearben

4
ขอบคุณสำหรับคำตอบ. แต่วิธีการ pet.setUserId (user.getId ()) ใน insertPetsForUser (User user, List <Pet> pets) จะตั้งค่า userId ได้อย่างไร? สมมติว่าคุณได้รับออบเจ็กต์ผู้ใช้จาก API และ ณ จุดนี้ไม่มี id (primaryKey) เนื่องจากไม่ได้รับการบันทึกลงใน RoomDb ดังนั้นการเรียก user.getId () จะให้ค่าเริ่มต้นเป็น 0 เท่านั้นสำหรับแต่ละ ผู้ใช้ตามที่ยังไม่ได้รับการบันทึก คุณจัดการอย่างไรเนื่องจาก user.getId () ส่งคืน 0 สำหรับผู้ใช้แต่ละคนเสมอ ขอบคุณ
Ikhiloya Imokhai

40

ไม่มีวิธีแก้ปัญหาแบบเนทีฟจนกว่าจะมีการอัปเดตในไลบรารีของห้อง แต่คุณสามารถทำได้โดยใช้เคล็ดลับ ค้นหาด้านล่างที่กล่าวถึง

  1. เพียงสร้างผู้ใช้ที่มีสัตว์เลี้ยง (ละเว้นสัตว์เลี้ยง) เพิ่ม getter และ setter สังเกตว่าเราต้องตั้งค่า Id ของเราเองในภายหลังและไม่สามารถใช้งานautogenerateได้

    @Entity
    public class User {
          @PrimaryKey
          public int id; 
    
          @Ignore
          private List<Pet> petList;
    }
    
  2. สร้างสัตว์เลี้ยง

    @Entity 
    public class Pet 
    {
        @PrimaryKey
        public int id;     
        public int userId; 
        public String name;
    }
    
  3. UserDao ควรเป็นคลาสนามธรรมแทนที่จะเป็นอินเทอร์เฟซ สุดท้ายแล้วใน 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;
    }
    

ปัญหาของคุณสามารถแก้ไขได้โดยไม่ต้องสร้าง UserWithPets POJO


2
ฉันชอบอันนี้เพราะมันหลีกเลี่ยง UserWithPets POJO โดยใช้วิธีการเริ่มต้น DAO สามารถเป็นอินเทอร์เฟซได้เช่นกัน ข้อเสียอย่างเดียวที่ฉันเห็นคือ insertUser () และ insertPetList () เป็นวิธีการสาธารณะ แต่ไคลเอนต์ไม่ควรใช้ ฉันใส่ขีดล่างหน้าชื่อของวิธีการเหล่านี้เพื่อแสดงว่าไม่ควรใช้ดังที่แสดงไว้ด้านบน
guglhupf

1
ใครสามารถแสดงวิธีนำสิ่งนี้ไปใช้อย่างถูกต้องในกิจกรรมได้หรือไม่? ฉันไม่รู้วิธีสร้างรหัสอย่างถูกต้อง
Philipp

@ ฟิลิปป์ฉันกำลังจัดการกับวิธีสร้างรหัสคุณพบวิธีแก้ปัญหาหรือไม่?
sochas

1
คุณทักมาหา @Philipp ฉันทำแบบเดียวกัน :)
sochas

2
ขอบคุณ @Philipp ฉันชอบคำตอบของคุณสำหรับความยืดหยุ่น! สำหรับการสร้างรหัสอัตโนมัติในกรณีของฉันที่ผมเรียกว่าinsertUser()เป็นครั้งแรกที่จะได้รับสร้างขึ้นโดยอัตโนมัติuserIdจากนั้นกำหนดว่าuserIdจะช่อง userId insertPet()ในชั้นเรียนสำหรับผู้รักสัตว์แล้วจะวนลูป
Robert

11

เนื่องจากห้องไม่ได้จัดการความสัมพันธ์ของเอนทิตีคุณจึงต้องตั้งค่าuserIdสัตว์เลี้ยงแต่ละตัวด้วยตัวคุณเองและช่วยชีวิตพวกมัน ตราบเท่าที่มีสัตว์เลี้ยงไม่มากเกินไปในคราวเดียวฉันจะใช้insertAllวิธีทำให้มันสั้น

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

ฉันไม่คิดว่าจะมีวิธีใดดีไปกว่านี้ในขณะนี้

เพื่อให้การจัดการง่ายขึ้นฉันจะใช้สิ่งที่เป็นนามธรรมในเลเยอร์เหนือ DAO:

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

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

    petDao.insertAll(pets);
}

ฉันลองใช้Streamไฟล์. ฉันแค่หวังว่าจะมีวิธีที่ดีกว่านี้
Akshay Chordiya

ฉันไม่คิดว่าจะมีวิธีที่ดีกว่านี้เนื่องจากเอกสารระบุว่าพวกเขาจงใจทิ้งการอ้างอิงออกจากไลบรารี: developer.android.com/topic/libraries/architecture/…
tknell

ฉันได้สร้างปัญหานี้Issetracker.google.com/issues/62848977ในเครื่องมือติดตามปัญหา หวังว่าพวกเขาจะทำบางอย่างเกี่ยวกับเรื่องนี้
Akshay Chordiya

1
ไม่ใช่วิธีแก้ปัญหาแบบเนทีฟ แต่คุณไม่จำเป็นต้องใช้อินเทอร์เฟซสำหรับ DAO คุณสามารถใช้คลาสนามธรรมได้เช่นกัน ... หมายความว่าวิธีการอำนวยความสะดวกสามารถนั่งภายใน DAO เองได้หากคุณไม่ต้องการมีคลาส wrapper ดูคำตอบของฉันสำหรับข้อมูลเพิ่มเติม ...
Kieran Macdonald-Hall

6

ขณะนี้ยังไม่มีวิธีแก้ปัญหานี้ ฉันได้สร้างhttps://issuetracker.google.com/issues/62848977บนเครื่องมือติดตามปัญหาของ Google และทีม Architecture Components กล่าวว่าพวกเขาจะเพิ่มโซลูชันดั้งเดิมในหรือหลัง v1.0 ของไลบรารีห้อง

วิธีแก้ปัญหาชั่วคราว:

ในขณะเดียวกันคุณสามารถใช้วิธีการแก้ปัญหาดังกล่าวโดยtknell

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

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

    petDao.insertAll(pets);
}

เมื่อมองไปที่โซลูชันอื่น ๆ ฉันเห็นวิธีที่จะดำเนินการนี้คือการใช้คลาสนามธรรมแทนอินเทอร์เฟซในขณะที่สร้าง dao และเพิ่มวิธีการที่เป็นรูปธรรมที่คล้ายกันเป็นส่วนหนึ่งของคลาสนามธรรมเหล่านั้น แต่ฉันยังไม่เข้าใจวิธีรับอินสแตนซ์เด็ก dao ด้วยวิธีการเหล่านี้ เช่น. คุณจะรับอินสแตนซ์ petDao ภายใน userDao ได้อย่างไร
Purush Pawar

5

ตอนนี้ที่ห้อง v2.1.0 ดูเหมือนจะไม่เหมาะสำหรับรุ่นที่มีความสัมพันธ์ซ้อนกัน จำเป็นต้องมีรหัสสำเร็จรูปจำนวนมากเพื่อรักษาไว้ เช่นการแทรกรายการด้วยตนเองการสร้างและการแมป ID โลคัล

การดำเนินการทำแผนที่ความสัมพันธ์นี้ทำนอกกรอบโดย Requery https://github.com/requery/requeryนอกจากนี้ยังไม่มีปัญหาในการแทรก Enums และมีตัวแปลงสำหรับประเภทที่ซับซ้อนอื่น ๆ เช่น URI


2

ฉันจัดการแทรกได้อย่างถูกต้องด้วยวิธีแก้ปัญหาที่ค่อนข้างง่าย นี่คือเอนทิตีของฉัน:

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

ฉันกำลังใช้ autoGenerate สำหรับค่าที่เพิ่มขึ้นอัตโนมัติ (ใช้แบบยาวกับคำอ้าง) นี่คือทางออกของฉัน:

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

ฉันมีปัญหาในการเชื่อมโยงเอนทิตีการสร้าง "formulaId = 0" ที่สร้างขึ้นโดยอัตโนมัติตลอดเวลา การแทรกเอนทิตีสูตรอาหารแก้ไขให้ฉันก่อน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.