66 votes

Type de retour pour Android Room joins

Supposons que je veuille faire un INNER JOIN entre deux entités Foo y Bar :

@Query("SELECT * FROM Foo INNER JOIN Bar ON Foo.bar = Bar.id")
List<FooAndBar> findAllFooAndBar();

Est-il possible de forcer un type de retour comme celui-ci ?

public class FooAndBar {
    Foo foo;
    Bar bar;
}

Lorsque j'essaie de le faire, j'obtiens cette erreur :

error: Cannot figure out how to read this field from a cursor.

J'ai également essayé d'aliaser les noms des tables pour qu'ils correspondent aux noms des champs, mais cela n'a pas fonctionné non plus.

Si ce n'est pas possible, comment puis-je construire proprement un type de retour compatible qui inclut tous les champs des deux entités ?

72voto

Gaëtan S Points 436

Dao

@Query("SELECT * FROM Foo")
List<FooAndBar> findAllFooAndBar();

Classe FooAndBar

public class FooAndBar {
    @Embedded
    Foo foo;

    @Relation(parentColumn =  "Foo.bar_id", entityColumn = "Bar.id")
    List<Bar> bar;
    // If we are sure it returns only one entry
    // Bar bar;

    //Getter and setter...
}

Cette solution semble fonctionner, mais je n'en suis pas très fier. Qu'en pensez-vous ?

Edit : Une autre solution

Dao, je préfère sélectionner explicitement mais "*" fera l'affaire :) Gardez à l'esprit que cette solution ne fonctionne que lorsque les champs des deux entités sont uniques. Voir les commentaires pour plus d'informations.

@Query("SELECT Foo.*, Bar.* FROM Foo INNER JOIN Bar ON Foo.bar = Bar.id")
List<FooAndBar> findAllFooAndBar();

Classe FooAndBar

public class FooAndBar {
    @Embedded
    Foo foo;

    @Embedded
    Bar bar;

    //Getter and setter...
}

edit : depuis la version 2.2.0-alpha01, l'annotation room @Relation peut gérer des relations One-To-One

1 votes

En cas de conflit entre Foo y Bar Je pense que vous pouvez les éliminer en créant une représentation de sous-ensemble de cette dernière classe, par exemple public class BareBar { ...some bar fields... } puis en ajoutant entity = BareBar.class à la @Relation . Voir : developer.Android.com/reference/Android/arch/persistence/room/

20 votes

La deuxième solution provoque l'erreur de compilation "Multiple fields have the same columnName" alors que les entités ont une propriété PK nommée de la même manière : id.

3 votes

La deuxième solution fonctionne-t-elle réellement ? Car j'obtiens l'erreur "Cannot figure out how to convert Cursor...". De plus, j'utilise @Embedded(prefix = "foo_") & @Embedded(prefix = "bar_")

18voto

AjahnCharles Points 1258

Une autre option consiste à écrire un nouveau POJO représentant la structure résultante de votre requête JOIN (qui prend même en charge le renommage des colonnes pour éviter les conflits) :

@Dao
public interface FooBarDao {
   @Query("SELECT foo.field1 AS unique1, bar.field1 AS unique2 "
          + "FROM Foo INNER JOIN Bar ON Foo.bar = Bar.id")
   public List<FooBar> getFooBars();

   static class FooBar {
       public String unique1;
       public String unique2;
   }
}    

Voir : room/accessing-data.html#query-multiple-tables

2 votes

Cela fonctionne lorsque les champs ont le même nom, il suffit d'avoir un alias pour eux.

13voto

dphans Points 126

Essayez cette méthode. Par exemple, j'ai des relations M2M (many-to-many) entre Product y Attribute . Nombreux Produits ont de nombreux Attributs et je dois obtenir tous les Attributs por Product.id avec des enregistrements triés par PRODUCTS_ATTRIBUTES.DISPLAY_ORDERING .

|--------------|  |--------------|  |-----------------------|
| PRODUCT      |  | ATTRIBUTE    |  | PRODUCTS_ATTRIBUTES   |
|--------------|  |--------------|  |-----------------------|
| _ID:  Long   |  | _ID: Long    |  | _ID: Long             |
| NAME: Text   |  | NAME: Text   |  | _PRODUCT_ID: Long     |
|______________|  |______________|  | _ATTRIBUTE_ID: Long   |
                                    | DISPLAY_ORDERING: Int |
                                    |_______________________|

Les modèles seront donc les suivants :

@Entity(
    tableName = "PRODUCTS",
    indices = [
        Index(value = arrayOf("NAME"), unique = true)
    ]
)
class Product {

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "_ID")
    var _id: Long = 0

    @ColumnInfo(name = "NAME")
    @SerializedName(value = "NAME")
    var name: String = String()

}

@Entity(
    tableName = "ATTRIBUTES",
    indices = [
        Index(value = arrayOf("NAME"), unique = true)
    ]
)
class Attribute {

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "_ID")
    var _id: Long = 0

    @ColumnInfo(name = "NAME")
    @SerializedName(value = "NAME")
    var name: String = String()

}

Et la table "join" sera :

@Entity(
    tableName = "PRODUCTS_ATTRIBUTES",
    indices = [
        Index(value = ["_PRODUCT_ID", "_ATTRIBUTE_ID"])
    ],
    foreignKeys = [
        ForeignKey(entity = Product::class, parentColumns = ["_ID"], childColumns = ["_PRODUCT_ID"]),
        ForeignKey(entity = Attribute::class, parentColumns = ["_ID"], childColumns = ["_ATTRIBUTE_ID"])
    ]
)
class ProductAttribute {

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "_ID")
    var _id: Long = 0

    @ColumnInfo(name = "_PRODUCT_ID")
    var _productId: Long = 0

    @ColumnInfo(name = "_ATTRIBUTE_ID")
    var _attributeId: Long = 0

    @ColumnInfo(name = "DISPLAY_ORDERING")
    var displayOrdering: Int = 0

}

En, AttributeDAO pour obtenir tous les attributs basés sur Product._ID vous pouvez faire quelque chose comme ci-dessous :

@Dao
interface AttributeDAO {

    @Query("SELECT ATTRIBUTES.* FROM ATTRIBUTES INNER JOIN PRODUCTS_ATTRIBUTES ON PRODUCTS_ATTRIBUTES._ATTRIBUTE_ID = ATTRIBUTES._ID INNER JOIN PRODUCTS ON PRODUCTS._ID = PRODUCTS_ATTRIBUTES._PRODUCT_ID WHERE PRODUCTS._ID = :productId ORDER BY PRODUCTS_ATTRIBUTES.DISPLAY_ORDERING ASC")
    fun getAttributesByProductId(productId: Long): LiveData<List<Attribute>>

}

Si vous avez des questions, n'hésitez pas à me les poser.

0 votes

Cette requête peut entraîner le gel de l'application lorsque les données des deux tables dépassent 1000. Comment éviter le gel de l'application lorsque la requête augmente et que le résultat de la requête augmente ?

1 votes

@SureshMaidaragi Utilisation avec une bibliothèque de pagination. Maintenant, changez la requête de retour de LiveData<List<Attribute>> en DataSource.Factory<Int, Attribute> . Par ailleurs, utilisez LIMIT la taille de la page à partir de la requête.

0 votes

Pourquoi utilisez-vous @SerializedName ?

3voto

CommonsWare Points 402670

Est-il possible de forcer un type de retour comme celui-ci ?

Vous pouvez essayer @Embedded annotations sur foo y bar . Cela indiquera à Room d'essayer de prendre les colonnes de votre requête et de les verser dans foo y bar instances. Je n'ai essayé qu'avec des entités, mais la documentation indique que cela devrait également fonctionner avec des POJO.

Cependant, cela peut ne pas fonctionner si vos deux tables ont des colonnes portant le même nom.

0 votes

Oui, cela ne fonctionne pas car mes entités ont des colonnes avec le même nom (comme id )...

2 votes

@pqvst : "comment construire proprement un type de retour compatible qui inclut tous les champs des deux entités ?" -- l'un ou l'autre choix foo o bar être @Embedded et mettre les champs restants directement dans FooAndBar ou mettre tous les champs directement dans FooAndBar . Utiliser AS dans le code SQL pour renommer les colonnes dupliquées dans le jeu de résultats, et utiliser @ColumnInfo si nécessaire pour cartographier ces AS -Les colonnes sont renommées en fonction de la nature des champs.

0 votes

C'est exactement ce que je veux éviter de faire... Cela ne me semble pas très "propre" :/

0voto

Shaon Points 225

Voici ma table d'alimentation

@Entity(tableName = "_food_table")
data class Food(@PrimaryKey(autoGenerate = false)
            @ColumnInfo(name = "_food_id")
            var id: Int = 0,
            @ColumnInfo(name = "_name")
            var name: String? = "")

Voici la table et la classe de modèle de mon panier (Food Cart)

@Entity(tableName = "_client_cart_table")
data class CartItem(
                @PrimaryKey(autoGenerate = false)
                @ColumnInfo(name = "_food_id")
                var foodId: Int? = 0,
                @Embedded(prefix = "_food")
                var food: Food? = null,
                @ColumnInfo(name = "_branch_id")
                var branchId: Int = 0)

Remarque : nous voyons ici la colonne _food_id dans deux tableaux. Cela provoquera une erreur au moment de la compilation. D'après la documentation @Embedded, vous devez utiliser un préfixe pour les différencier.

A l'intérieur du dao

@Query("select * from _client_cart_table inner join _food_table on _client_cart_table._food_id = _food_table._food_id where _client_cart_table._branch_id = :branchId")
fun getCarts(branchId: Int) : LiveData<List<CartItem>>

Cette requête renverra des données comme suit

CartItem(foodId=5, food=Food(id=5, name=Black Coffee), branchId=1)

C'est ce que j'ai fait dans mon projet. Essayez donc. Bon codage

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X