简体   繁体   中英

Cannot find the child entity column `id` error in Room db

I make a Room database and have the Entity Data classes CastDb and CastDbModel. When I build the project I get the following error. But the thing is that CastDb class doesn't suppose to have an id field. Any Idea how to fit it?

Cannot find the child entity column `id` in com.mvvm.data.db.entities.CastDb. Options: name, profile_path, character
    private com.mvvm.data.db.convertermodels.Cast cast;
@Entity(tableName = "cast_model")
data class CastDbModel(
    @PrimaryKey(autoGenerate = false)
    var id : Int,
    @ColumnInfo (name = "cast")
    var cast : Cast
)
{
    constructor() : this(0, Cast(CastDb()))
}

@Entity(tableName = "cast")
data class CastDb(
    @PrimaryKey(autoGenerate = false)
    var name: String,
    var profile_path: String,
    var character: String)

{
    constructor() : this("", "", "")
}

Also, I have a converter model class Cast

data class Cast(
    @Embedded
    var cast: CastDb
)

Converter class:

class CastConverter {
    @TypeConverter
    fun fromCast(value: Cast): String {
        return Gson().toJson(value)
    }
    @TypeConverter
    fun toCast(value: String): Cast {
        return Gson().fromJson(value, Cast::class.java)
    }
}

The code you have supplied is fine as far as compiling is concerned. I suspect that your issue with code elsewhere that utilises the CastDB, perhaps (considering it mentions child column ) an attempt to form a relationship where the ClassDBModel class has been inferred but that the Cast class has been provided (and hence why the id column is missing).

So the error is either in some other code or possibly that you have issues with the dependencies. As such you need to locate the other code or check the Room dependencies including version (are you using the same version?, are you using the latest stable version (2.4.3 or perhaps 2.5.0-alpha02)?).

However , I think you are getting mixed up with relationships and what data should be stored where.

With the code above you have two database tables cast_model and cast where the cast_model contains(embeds) a cast . So you would appear to be trying to save the same data twice (if not then as it stands the cast table is redundant)

Using your code plus the following in an @Dao annotated interface:-

@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(castDbModel: CastDbModel): Long
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(castDb: CastDb): Long
@Query("SELECT * FROM cast")
fun getAllCast(): List<Cast>
@Query("SELECT * FROM cast_model")
fun getAllFromCastModel(): List<CastDbModel>

And then using the code in an activity (note run on the main thread for brevity and convenience):-

    castdb = CastDatabase.getInstance(this) /* Get an instance of the database */
    castDao = castdb.getCastDao() /* get the Dao instance */
    /* Prepare some Cast objects (with embedded CastDb objects)  not store in the database as yet */
    val castdb_et = CastDb(name = "Elizabeth Taylor","etaylor.text","Kate")
    val castdb_rb = CastDb("Richard Burton","rburton.txt","Petruchio")
    val castdb_cc = CastDb("Cyril Cusack","ccusack.txt","Grumio")
    /* Store the Cast objects in the cast table */
    castDao.insert(castdb_et)
    castDao.insert(castdb_rb)
    castDao.insert(castdb_cc)
    /* Create and store some CastDbModels in the cast_model table */
    castDao.insert(CastDbModel(100,Cast(castdb_et))) /* already in the cast table */
    /* following cast is not in the cast table */
    castDao.insert(CastDbModel(200,Cast( CastDb("Natasha Pyne","npyne.txt","Bianca")))) /* not in the cast table */
    castDao.insert(CastDbModel(300,Cast(castdb_cc))) /* already in the cast table */

    /* Get all of the cast's stored in the database and output them to the log */
    for(c in castDao.getAllCast()) {
        Log.d("CASTINFO","Name is ${c.cast.name} Profile is ${c.cast.profile_path} Character is ${c.cast.character}")
    }
    /* Get all the castDBModels stored in the database  and output them to the log  */
    for (cm in castDao.getAllFromCastModel()) {
        Log.d("CASTMODELINFO","CastModel ID is ${cm.id} Name is ${cm.cast.cast.name} Character is ${cm.cast.cast.character} ")
    }

The log contains (as expected):-

D/CASTINFO: Name is Elizabeth Taylor Profile is etaylor.text Character is Kate
D/CASTINFO: Name is Richard Burton Profile is rburton.txt Character is Petruchio
D/CASTINFO: Name is Cyril Cusack Profile is ccusack.txt Character is Grumio


D/CASTMODELINFO: CastModel ID is 100 Name is Elizabeth Taylor Character is Kate 
D/CASTMODELINFO: CastModel ID is 200 Name is Natasha Pyne Character is Bianca 
D/CASTMODELINFO: CastModel ID is 300 Name is Cyril Cusack Character is Grumio

The actual data stored in the database (via App Inspection):-

In the cast table:- 在此处输入图像描述 In the cast_model table:- 在此处输入图像描述

  • So you have a situation where you are either storing the same data twice and wasting storage (an probably later complicating matters such as updating data) or one of the tables is redundant.

If you are trying to relate one table to the other then there is no need eg why would you say, for example get Elizabeth Taylor from the cast to then get "Elizabeth Taylor" from the cast_model table, other than the id column (which has no use beyond uniquely identifying the row) the exact same data exists in both tables.

I suspect that what you want is a table with say Elizabeth Taylor's details and then perhaps a play/film where she and others, such as Richard Burton, Cyril Cusack and Natasha Pyne were characters in the play/film.

In which case you would probably want:-

  • a table for the actors/artists themselves
  • a table for the play/films
  • a table for the cast of the film (that links the character with the artist to the film).
    • often due to the object orientated approach, this is seen as a list objects that contain the character and the artist, which as objects cannot be directly stored, so converted to a JSON representation of the entire list and held in (in this case) the play/film table.

As such I believe that there is a good chance that you need to review the underlying design .

As an example (assuming films with cast (characters)) then perhaps the following may help you.

First the 3 tables ie the @Entity annotated classes for Films, Artists and Characters/Cast where a row in the character table has a reference (relationship) to the film and also to the artist, as well as some data (profile of the character). So similar to what you appear to be wanting:-

@Entity
data class Artist(
    @PrimaryKey
    var artistId: Long?=null,
    var artistName: String,
    var artistDOB: String
    )
@Entity
data class Film(
    @PrimaryKey
    var filmId: Long?=null,
    var filmName: String,
    var filmYear: Int
    )
@Entity(
    primaryKeys = ["artistIdReference","filmIdReference"],
    /*Optional but enforces referential integrity*/
    foreignKeys = [
        ForeignKey(
            Artist::class,
            parentColumns = ["artistId"],
            childColumns = ["artistIdReference"],
            /* Optional but helps to maintain referential integrity */
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE
        ),
        ForeignKey(
            Film::class,
            parentColumns = ["filmId"],
            childColumns = ["filmIdReference"],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE
        )
    ]
)
data class Character(
    var characterName: String,
    var characterProfile: String,
    var artistIdReference: Long,
    @ColumnInfo(index = true)
    var filmIdReference: Long
)

Then some POJO's for combining the related data:-

data class CharacterWithArtist(
    @Embedded
    var character: Character,
    @Relation(
        entity = Artist::class,
        parentColumn = "artistIdReference",
        entityColumn = "artistId"
    )
    var artist: Artist
)
data class FilmWithCharacters(
    @Embedded
    var film: Film,
    @Relation(
        entity = Character::class,
        parentColumn = "filmId",
        entityColumn = "filmIdReference"
    )
    var characterWithArtistList: List<CharacterWithArtist>
)

Then an @Dao annotated interface, for function to insert data and also one to extract Films with the cast including the artists.

A pretty standard @Database (other than for convenience and brevity running on the main thread is allowed):-

@Database(entities = [Artist::class,Film::class,Character::class], exportSchema = false, version = 1)
abstract class TheDatabase: RoomDatabase() {
    abstract fun getAllDao(): AllDao

    companion object {
        private var instance: TheDatabase? = null
        fun getInstance(context: Context): TheDatabase {
            if (instance==null) {
                instance = Room.databaseBuilder(context,TheDatabase::class.java,"the_database.db")
                    .allowMainThreadQueries()
                    .build()
            }
            return instance as TheDatabase
        }
    }
}

With all the above, perhaps note that no Type Converters are required, also not some of the comments (such as in regard to the Foreign Keys).

Finally putting it all together in an activity that adds two Films, 4 artists, and then for the first film adds the cast/characters for the film. After inserting the data, the data is extracted and output to the log handling the related data accordingly to provide The Film and the cast with the actor:-

class MainActivity : AppCompatActivity() {
    lateinit var db: TheDatabase
    lateinit var dao: AllDao
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        db = TheDatabase.getInstance(this)
        dao = db.getAllDao()

        val etaylorId = dao.insert(Artist(artistName = "Elizabeth Taylor", artistDOB = "1932-02-27"))
        val rburtonId = dao.insert(Artist(artistName = "Richard Burton", artistDOB = "1925-11-10"))
        val ccusackId = dao.insert(Artist(artistName = "Cyril Cusack", artistDOB = "1910-11-26"))
        val npyneId = dao.insert(Artist(artistName = "Natasha Pyne", artistDOB = "1946-07-09"))

        val tots = dao.insert(Film(filmName = "Taming of the Shrew", filmYear = 1967))
        val sw = dao.insert(Film(filmName = "Star Wars", filmYear = 1997))

        dao.insert(Character(characterName = "Kate","Katherina (Kate) Minola is a fictional character in William Shakespeare's play The Taming of the Shrew.",etaylorId,tots))
        dao.insert(Character(characterName = "Petruchio","Tamer of Kate",rburtonId,tots))
        dao.insert(Character(characterName = "Grumio", characterProfile = "blah",ccusackId,tots))
        dao.insert(Character(characterName = "Bianca","Kate's sister",npyneId,tots))

        for(fwc in dao.getAllFilmsWithCast()) {
            val sb = StringBuilder()
            for (cwa in fwc.characterWithArtistList) {
                sb.append("\n\t Character is ${cwa.character.characterName} played by ${cwa.artist.artistName} \n\t\t${cwa.character.characterProfile}")

            }
            Log.d(
                "FILMINFO",
                "Film is ${fwc.film.filmName} (id is ${fwc.film.filmId}) made in ${fwc.film.filmYear}. Cast of ${fwc.characterWithArtistList.size}. They are $sb")
        }
    }
}

The Result

The log includes:-

D/FILMINFO: Film is Taming of the Shrew (id is 1) made in 1967. Cast of 4. They are 
         Character is Kate played by Elizabeth Taylor 
            Katherina (Kate) Minola is a fictional character in William Shakespeare's play The Taming of the Shrew.
         Character is Petruchio played by Richard Burton 
            Tamer of Kate
         Character is Grumio played by Cyril Cusack 
            blah
         Character is Bianca played by Natasha Pyne 
            Kate's sister
            
            
D/FILMINFO: Film is Star Wars (id is 2) made in 1997. Cast of 0. They are 


The store objects version



As previously mentioned, often you will see objects stored instead of utilising the relationship aspect and the objects, as they cannot be stored directly as JSON representations of the object or a List/Array of objects.

Adapting the above example, it might be that the castlist would be stored as part of the Film as a list of characters and the artists playing the characters.

So AltFilm (so that both Film and AltFilm can co-exist) could be derived from:-

data class CastList(
    var castList: List<CharacterWithArtist>
)
@TypeConverters(RoomTypeConverters::class)
@Entity
data class AltFilm(
    @PrimaryKey
    var filmId: Long?=null,
    var filmName: String,
    var filmYear: Int,
    var castList: CastList
)
class RoomTypeConverters {
    @TypeConverter
    fun convertFromCastListToJSONString(castList: CastList): String = Gson().toJson(castList)
    @TypeConverter
    fun convertFromJSONStringToCastList(jsonString: String): CastList = Gson().fromJson(jsonString,CastList::class.java)
}
  • CastList being a single object that consists of a List of CharacterWithArtist (ie Artist and Character combined (to utilise the existing classes (which even though annotated with @Entity for use with the first methodology, is ignored ie for this usage case they can be considered to not be annotated with @Entity)))
  • The two TypeConverters do as their function names say.

To facilitate use of the above, then two additional functions in the @Dao annotated class AllDao:-

@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(altFilm: AltFilm): Long
@Query("SELECT * FROM altFilm")
fun getAllFromAltFilm(): List<AltFilm>

Now to compare the two methodologies (utilisation of Relationships v storing objects as JSON) then the activity code can be changed to be (will add the same Films "Taming of the Shrew " and "Taming of the Shrew 2" using both methods):-

class MainActivity : AppCompatActivity() {
    lateinit var db: TheDatabase
    lateinit var dao: AllDao
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        db = TheDatabase.getInstance(this)
        dao = db.getAllDao()

        val etaylorId = dao.insert(Artist(artistName = "Elizabeth Taylor", artistDOB = "1932-02-27"))
        val rburtonId = dao.insert(Artist(artistName = "Richard Burton", artistDOB = "1925-11-10"))
        val ccusackId = dao.insert(Artist(artistName = "Cyril Cusack", artistDOB = "1910-11-26"))
        val npyneId = dao.insert(Artist(artistName = "Natasha Pyne", artistDOB = "1946-07-09"))

        val tots = dao.insert(Film(filmName = "Taming of the Shrew", filmYear = 1967))
        val sw = dao.insert(Film(filmName = "Star Wars", filmYear = 1997))
        val tots2 = dao.insert(Film(filmName = "Taming of the Shrew 2", filmYear = 1967))

        dao.insert(Character(characterName = "Kate","Katherina (Kate) Minola is a fictional character in William Shakespeare's play The Taming of the Shrew.",etaylorId,tots))
        dao.insert(Character(characterName = "Petruchio","Tamer of Kate",rburtonId,tots))
        dao.insert(Character(characterName = "Grumio", characterProfile = "blah",ccusackId,tots))
        dao.insert(Character(characterName = "Bianca","Kate's sister",npyneId,tots))
        /* Add a second film with the same cast (for comparison) of the 2 methodologies */
        dao.insert(Character(characterName = "Kate","Katherina (Kate) Minola is a fictional character in William Shakespeare's play The Taming of the Shrew.",etaylorId,tots2))
        dao.insert(Character(characterName = "Petruchio","Tamer of Kate",rburtonId,tots2))
        dao.insert(Character(characterName = "Grumio", characterProfile = "blah",ccusackId,tots2))
        dao.insert(Character(characterName = "Bianca","Kate's sister",npyneId,tots2))

        /*Sort of replicate the above but with some changes to the actual data so it is easy to distinguish when comparing */
        /* Ignoring the Start Wars Film that has not cast 2 Taming of the Shrew films with JSON representation of the cast */
        dao.insert(
            AltFilm(
                filmName = "Taming of the Shrew",
                filmYear = 1967,
                castList = CastList(
                listOf(
                    CharacterWithArtist(
                        Character("Kate","The Shrew",etaylorId,tots),
                        Artist(artistName = "Liz Taylor", artistDOB = "01-01-1900")
                    ),
                    CharacterWithArtist(
                        Character("Petruchio","Shrew Tamer",rburtonId,tots),
                        Artist(artistName = "Dicky Burton", artistDOB = "02-02-1901")
                    )
                )
                )
            )
        )
        dao.insert(
            AltFilm(
                filmName = "Taming of the Shrew 2",
                filmYear = 1967,
                castList = CastList(
                    listOf(
                        CharacterWithArtist(
                            Character("Kate","The Shrew",etaylorId,tots),
                            Artist(artistName = "Liz Taylor", artistDOB = "01-01-1900")
                        ),
                        CharacterWithArtist(
                            Character("Petruchio","Shrew Tamer",rburtonId,tots),
                            Artist(artistName = "Dicky Burton", artistDOB = "02-02-1901")
                        )
                    )
                )
            )
        )
        /* Extract an output the 2 films via the relationships (Film, Artist and Character tables) */
        for(fwc in dao.getAllFilmsWithCast()) {
            val sb = StringBuilder()
            for (cwa in fwc.characterWithArtistList) {
                sb.append("\n\tCharacter is ${cwa.character.characterName} played by ${cwa.artist.artistName} \n\t\t${cwa.character.characterProfile}")

            }
            Log.d(
                "FILMINFO",
                "Film is ${fwc.film.filmName} (id is ${fwc.film.filmId}) made in ${fwc.film.filmYear}. Cast of ${fwc.characterWithArtistList.size}. They are $sb")
        }
        /* Do the same for the 2 films via the embedded JSON representation of the cast */
        for (af in dao.getAllFromAltFilm()) {
            val sb = StringBuilder()
            for(cwa in af.castList.castList) {
                sb.append("\n\tCharacter is ${cwa.character.characterName} played by ${cwa.artist.artistName} \n\t\t${cwa.character.characterProfile}")
            }
            Log.d(
                "ALTFILM",
                "Film(alt) is ${af.filmName} (id is ${af.filmId}) made in ${af.filmYear}. Cast of ${af.castList.castList.size}. They are $sb"
            )
        }
    }
}

The output showing that basically the same data (albeit with a intended changes, such as fewer characters and different names and DOBs) can be stored/retrieved either way:-

D/FILMINFO: Film is Taming of the Shrew (id is 1) made in 1967. Cast of 4. They are 
        Character is Kate played by Elizabeth Taylor 
            Katherina (Kate) Minola is a fictional character in William Shakespeare's play The Taming of the Shrew.
        Character is Petruchio played by Richard Burton 
            Tamer of Kate
        Character is Grumio played by Cyril Cusack 
            blah
        Character is Bianca played by Natasha Pyne 
            Kate's sister
            
D/FILMINFO: Film is Star Wars (id is 2) made in 1997. Cast of 0. They are 


D/FILMINFO: Film is Taming of the Shrew 2 (id is 3) made in 1967. Cast of 4. They are 
        Character is Kate played by Elizabeth Taylor 
            Katherina (Kate) Minola is a fictional character in William Shakespeare's play The Taming of the Shrew.
        Character is Petruchio played by Richard Burton 
            Tamer of Kate
        Character is Grumio played by Cyril Cusack 
            blah
        Character is Bianca played by Natasha Pyne 
            Kate's sister
            

D/ALTFILM: Film(alt) is Taming of the Shrew (id is 1) made in 1967. Cast of 2. They are 
        Character is Kate played by Liz Taylor 
            The Shrew
        Character is Petruchio played by Dicky Burton 
            Shrew Tamer
D/ALTFILM: Film(alt) is Taming of the Shrew 2 (id is 2) made in 1967. Cast of 2. They are 
        Character is Kate played by Liz Taylor 
            The Shrew
        Character is Petruchio played by Dicky Burton 
            Shrew Tamer

However, comparing the tables (via App Inspection), shows quite a marked difference

Film Table v AltFilm Table

在此处输入图像描述 versus在此处输入图像描述

As can be seen in addition to the actual data the JSON representation of the CastList objects contains a great deal of bloat to enable it to be converted back into a CatList object and this bloat will be repeated throughout not only the column but from row to row.

That in comparison to storing just the core data just once per Artist (see previous image of the Artist table).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM