【问题标题】:Making pre-packed .db file for pre-populating a Room database制作用于预填充 Room 数据库的预打包 .db 文件
【发布时间】:2019-12-07 07:50:54
【问题描述】:

假设需要一个 .db 文件来预填充 Room 数据库,但运行时出现问题Pre-packaged database has an invalid schema。在许多列中,只有两个不匹配,它们在 Kotlin 中都定义为 Boolean 类型。

Kotlin 实体类:

@ColumnInfo(name = "xxx")
var xxx: Boolean = false ,

@ColumnInfo(name = "yyy")
var yyy: Boolean? = null

错误信息:

Expected:
    TableInfo{name='zzz', columns={xxx=Column{name='xxx', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, yyy=Column{name='yyy', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, foreignKeys=[], indices=[]}
Found:
    TableInfo{name='zzz', columns={xxx=Column{name='xxx', type='NUMERIC', affinity='1', notNull=true, primaryKeyPosition=0, defaultValue='null'}, yyy=Column{name='yyy', type='NUMERIC', affinity='1', notNull=false, primaryKeyPosition=0, defaultValue='null'}, foreignKeys=[], indices=[]}

用于创建预打包的 .db 文件 DB Browser for SQLite,这是用于创建实际 zzz 表的查询:

CREATE TABLE "zzz" (
    "id"    INTEGER PRIMARY KEY AUTOINCREMENT,
    ...
    "xxx"   INTEGER NOT NULL DEFAULT 0,
    "yyy"   INTEGER DEFAULT NULL
);

尽管 xxx 和 yyy 列被定义为 INTEGER 类型,但它们最终还是 NUMERIC 类型。

【问题讨论】:

    标签: android android-sqlite android-room


    【解决方案1】:

    打开读取和检查的预打包数据库有一个架构,其中 xxx 和 yyy 的列类型不是 INTEGER,而是 NUMERIC(或被认为是 NUMERIC)。

    Room 不处理 NUMERIC 类型,因为它基本上是包罗万象的类型,因此它无法明确确定应该用于成员变量的类型,它只能猜测。因此,该消息表示它认为预打包的数据库不适合其化身。

    您需要使用将 xxx 和 zzz 列设置为 INTEGER 的架构重新创建预打包的数据库,例如使用:-

    CREATE TABLE "zzz" (
        "id"    INTEGER PRIMARY KEY AUTOINCREMENT,
        ...
        "xxx"   INTEGER NOT NULL DEFAULT 0,
        "yyy"   INTEGER DEFAULT NULL
    );
    

    或者您需要更改预打包数据库中的 zzz 表,以便架构如上。简而言之,Room 不会处理已定义的具有 NUMERIC 派生类型的列,因为它不知道如何处理 NUMERIC 这样的类型,因为类型是一个包罗万象的类型。

    要更改表格,您可以使用以下内容:-

    DROP TABLE IF EXISTS zzz_amended;
    CREATE TABLE IF NOT EXISTS "zzz_amended" (
        "id"    INTEGER PRIMARY KEY AUTOINCREMENT,
        /* ... */
        "xxx"   INTEGER NOT NULL DEFAULT 0,
        "yyy"   INTEGER DEFAULT NULL
    );
    INSERT INTO zzz_amended SELECT * FROM zzz;
    DROP TABLE IF EXISTS zzz_old;
    ALTER TABLE zzz RENAME TO zzz_old;
    ALTER TABLE zzz_amended RENAME TO zzz;
    DROP TABLE IF EXISTS zzz_old;
    

    更简单的替代方法是使用 RoomDatabaseBuildercreateFromAssetcreateFromFile 方法,但这可能需要更改实体以适合确定架构的方法。


    补充说明:-

    嗯,用于表 zzz 的架构如图所示,其中 xxx 和 yyy 是整数类型。并且正在使用 createFromAsset() 方法。但 由于某种原因 createFromAsset() 的结果是 xxx 和 yyy 是 NUMERIC 类型,而不是创建时定义的 INTEGER 类型 zzz 表。

    1.

    考虑以下数据库,其中的表已按照您所说的创建。例如

    按以下方式填充:-

    数据库在so59224033.db提供

    2

    还可以考虑以下简单应用程序:-

    Zzz

    @Entity(tableName = "zzz")
     data class Zzz (
    
        @PrimaryKey(autoGenerate = true)
        var id: Long? = null,
    
        @ColumnInfo(name = "xxx")
        var xxx: Boolean = false ,
    
        @ColumnInfo(name = "yyy")
        var yyy: Boolean? = null
    )
    

    ZzzDao

    @Dao
    interface ZzzDao {
    
        @Query("SELECT * FROM zzz")
        fun getAllFromzzz() :List<Zzz>
    }
    

    应用数据库

    @Database(version = 1,entities = [Zzz::class])
    abstract class AppDatabase :RoomDatabase() { 
        abstract fun getZzzDao() :ZzzDao
    }
    

    MainActivity

    class MainActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            val appDatabase :AppDatabase = Room.databaseBuilder(this,AppDatabase::class.java,"mydb")
                .allowMainThreadQueries()
                .createFromAsset("so59224033.db")
                .build()
    
            val listofZzz = appDatabase.getZzzDao().getAllFromzzz()
            for (z  in listofZzz) {
                Log.d("ZzzINFO","ID = " + z.id + " xxx = " + z.xxx + " yyy = " + z.yyy)
            }
        }
    }
    

    3

    结果没有任何冲突(因此消除了 Room 的错误,至少如果使用最新的 Room 库,以及可用的代码):-

    kapt 'androidx.room:room-compiler:2.2.2'
    implementation 'androidx.room:room-runtime:2.2.2'
    

    结果如预期:-

    2019-12-08 08:55:23.196 19370-19370/? D/ZzzINFO: ID = 1 xxx = false yyy = null
    2019-12-08 08:55:23.196 19370-19370/? D/ZzzINFO: ID = 2 xxx = false yyy = null
    2019-12-08 08:55:23.196 19370-19370/? D/ZzzINFO: ID = 3 xxx = false yyy = null
    2019-12-08 08:55:23.196 19370-19370/? D/ZzzINFO: ID = 4 xxx = false yyy = null
    2019-12-08 08:55:23.196 19370-19370/? D/ZzzINFO: ID = 5 xxx = false yyy = null
    2019-12-08 08:55:23.196 19370-19370/? D/ZzzINFO: ID = 6 xxx = false yyy = null
    2019-12-08 08:55:23.196 19370-19370/? D/ZzzINFO: ID = 7 xxx = false yyy = null
    2019-12-08 08:55:23.196 19370-19370/? D/ZzzINFO: ID = 8 xxx = false yyy = null
    2019-12-08 08:55:23.196 19370-19370/? D/ZzzINFO: ID = 9 xxx = false yyy = null
    2019-12-08 08:55:23.196 19370-19370/? D/ZzzINFO: ID = 10 xxx = false yyy = null
    2019-12-08 08:55:23.196 19370-19370/? D/ZzzINFO: ID = 11 xxx = false yyy = null
    2019-12-08 08:55:23.196 19370-19370/? D/ZzzINFO: ID = 12 xxx = false yyy = null
    2019-12-08 08:55:23.196 19370-19370/? D/ZzzINFO: ID = 13 xxx = false yyy = null
    2019-12-08 08:55:23.196 19370-19370/? D/ZzzINFO: ID = 14 xxx = false yyy = null
    2019-12-08 08:55:23.196 19370-19370/? D/ZzzINFO: ID = 15 xxx = false yyy = null
    2019-12-08 08:55:23.196 19370-19370/? D/ZzzINFO: ID = 16 xxx = false yyy = null
    2019-12-08 08:55:23.196 19370-19370/? D/ZzzINFO: ID = 17 xxx = false yyy = null
    2019-12-08 08:55:23.197 19370-19370/? D/ZzzINFO: ID = 18 xxx = false yyy = null
    2019-12-08 08:55:23.197 19370-19370/? D/ZzzINFO: ID = 19 xxx = false yyy = null
    2019-12-08 08:55:23.197 19370-19370/? D/ZzzINFO: ID = 20 xxx = false yyy = null
    

    4

    如果源数据库被更改,例如使用:-

    那么结果是:-

     Expected:
    TableInfo{name='zzz', columns={yyy=Column{name='yyy', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, xxx=Column{name='xxx', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=[]}
     Found:
    TableInfo{name='zzz', columns={yyy=Column{name='yyy', type='NUMERIC', affinity='1', notNull=false, primaryKeyPosition=0, defaultValue='NULL'}, xxx=Column{name='xxx', type='typethatwillbe_N_U_M_E_R_I_C', affinity='1', notNull=true, primaryKeyPosition=0, defaultValue='0'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=[]}
    

    即您的问题已通过使用资产文件夹中不符合预期的数据库复制。

    即原因是assets文件夹中的数据库不正确。

    我建议从 assets 文件夹中删除文件,仔细检查列是否定义为 INTEGER,然后将文件复制到 assets 文件夹中。

    【讨论】:

    • 感谢迈克的回复。那么用于表zzz 的架构如图所示,其中xxxyyy 的类型为INTEGER。并且正在使用createFromAsset() 方法。但由于某种原因,createFromAsset() 的结果是xxxyyy 的类型为NUMERIC,而不是创建zzz 表时定义的INTEGER 类型。问题是,为什么会发生这种情况,如何解决它,或者除了我使用过的DB Browser for SQLite 之外,还有其他更好的工具可以用来创建预打包的 .db 文件。
    • @J.Grbo createFromAsset 中存在错误(请参阅修改后的答案),或者预打包的数据库没有您认为它具有的架构。我会冒险押注后者。答案已更新,演示表明给定的架构产生了正确的结果。
    • @J.Grbo 现在已在进一步修改的 asnwer 中显示,资产文件夹中每个填充的数据库中的架构是问题所在。
    • 我已经从 createFromAsset 转移到在创建数据库以预填充它时提供回调,因为我已经在这个上花费了很多时间而没有任何成功。当我回复你未经编辑的答案时,我告诉自己我会再试一次,但因为我正在做其他事情,所以推迟了它,但当我看到你花时间测试它时,我已经重新-再次实现它,它的工作原理。
    • 不知道今天发生了什么,我不是仔细检查,而是 8x 检查用于创建表的 SQL 查询是否与我在此处发布的一样。不过问题还是解决了。非常感谢 Mike 抽出宝贵的时间。
    猜你喜欢
    • 1970-01-01
    • 2018-06-27
    • 1970-01-01
    • 2019-01-23
    • 1970-01-01
    • 1970-01-01
    • 2019-09-08
    • 2021-11-29
    • 1970-01-01
    相关资源
    最近更新 更多