关于为与 Room 中的另一个实体具有一对多关系的实体插入记录的问题

Question about inserting records for an entity that has one-to-many relationship with another entity in Room

我目前正在为我的应用程序数据库使用 Android Room,并且我有一个名为 Transaction 的现有实体,这是它的简化版本:

@Entity(tableName = "transactions", indices = {@Index(value = "id", unique = true)})
public class Transaction {

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id")
    private int txnId;

    private String type;
}

我需要将一个名为 Product 的对象添加到 Transaction 对象。每个 Transaction 个对象可以有多个 Products。根据我读过的内容,我将 Product 定义为具有对应于 Transaction 对象的 txnId 的外键:

@Entity(tableName = "products",
    foreignKeys = @ForeignKey(entity = Transaction.class,
    parentColumns = "id",
    childColumns = "txn_id",
    onDelete = CASCADE))
public class Product {

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id")
    private int productId;

    private String productCode;

    private int quantity;

    @ColumnInfo(name = "txn_id")
    private int txnId;
}

我想,为了获得与 Transaction 记录关联的所有 Products,我在我的 ProductDao 接口中做了以下方法:

@Query("SELECT * FROM products WHERE txn_id LIKE :txn_id")
Observable<List<Product>> getProductsByTxnId(int txn_id);

现在我的问题来了:例如,如果我向数据库中插入一个 Transaction,我将如何为那个 Transaction 添加多个 Products?我是否需要抓住那个特定 Transactionid,在插入之前将其设置为我的 Product 对象中相应的 txn_id,或者 Room 会自动插入如果您已将 Product 实体设置为具有引用 Transactionclass?

foreignKey,则一切正常

how would I add multiple Products for that Transaction?

您可以使用 mapping/associative/relationship(和其他名称)table(实体)来反映 many-many 关系(即许多交易可以用于(与)单个产品许多产品可由单个交易使用)。

这是一个 table,主要有 2 列,一列用于与一个部分(交易)相关(唯一标识)的值,另一列用于与另一个(产品)相关的值。

假设 交易 1产品 2,3,4,5 & 6,以及具有 产品 1,3,5,7,9

交易 2

映射 table 将包含:-

1 2
1 3
1 4
1 5
1 6
2 1
2 3
2 9
2 7
2 5

Do I need to grab a hold of the id for that particular Transaction, set it to the corresponding txn_id in my Product object before inserting it, or does Room automatically insert things correctly if you have setup the Product entity to have a foreignKey referencing the Transactionclass?

对于SQLite外键(Room or not),它只是定义了一个约束(规则),即子列值中的值必须是父列中的值。

没有什么魔法可以决定这些值(如果你仔细想想,它怎么知道什么与什么相关)。您必须在插入时以编程方式确定值。

所以答案是你需要毕业 id's

映射和外键使用示例

也许考虑以下:-

DROP TABLE IF EXISTS Map1;
DROP TABLE IF EXISTS Map2;
DROP TABLE IF EXISTS Transactions;
DROP TABLE IF EXISTS Products;

CREATE TABLE IF NOT EXISTS Transactions (id INTEGER PRIMARY KEY, name TEXT);
CREATE TABLE IF NOT EXISTS Products (id INTEGER PRIMARY KEY, name TEXT);
INSERT INTO Transactions (name) VALUES('Take'),('Give'),('Buy'),('Sell');
INSERT INTO Products(name) VALUES('Tea'),('Coffee'),('Water'),('Beer');
/* Mapping table without foreign keys */
CREATE TABLE IF NOT EXISTS Map1 (
    txn_id INTEGER,
    prd_id INTEGER, 
    UNIQUE(txn_id,prd_id) /* <- prevents duplication */
);
/* Mapping Table with foreign keys and cascading deletes and updates */
CREATE TABLE IF NOT EXISTS Map2 (
    txn_id INTEGER 
        REFERENCES Transactions(id) 
            ON DELETE CASCADE /* if parent is deleted then all children that map to that parent are deleted (only the maps rows)  */
            ON UPDATE CASCADE, /* like wise for updates (only if the id is updated) */
    prd_id INTEGER 
        REFERENCES Products(id) 
            ON DELETE CASCADE 
            ON UPDATE CASCADE, 
    UNIQUE(txn_id,prd_id)
);
INSERT INTO Map1 VALUES(1,1),(1,4),(2,3),(2,2),(3,1),(3,2),(3,3),(3,4),(4,2);
INSERT INTO Map2 VALUES(1,1),(1,4),(2,3),(2,2),(3,1),(3,2),(3,3),(3,4),(4,2);

/*Result 1 Mapped via Map1 table without foreign keys */
SELECT Transactions.name AS TransactionName, Products.name AS ProductName, 
    'Transaction '||txn_id||' maps to Product '||prd_id AS mapping
FROM Transactions 
JOIN Map1 ON Transactions.id = Map1.txn_id
JOIN Products ON Map1.prd_id = Products.id
;

/* Result 2 Mapped via Map2 table that has Foreign keys (no difference) to Result1 */
SELECT Transactions.name AS TransactionName, Products.name AS ProductName,
    'Transaction '||txn_id||' maps to Product '||prd_id AS mapping
FROM Transactions 
JOIN Map2 ON Transactions.id = Map2.txn_id
JOIN Products ON Map2.prd_id = Products.id
;

/* Add a rouge mapping entry to Map1 */
INSERT INTO Map1 VALUES (5,6); /* oooops niether transaction or Product exists */

/* Result 3 no issues with useless rouge entry */
SELECT Transactions.name AS TransactionName, Products.name AS ProductName, 
    'Transaction '||txn_id||' maps to Product '||prd_id AS mapping
FROM Transactions 
JOIN Map1 ON Transactions.id = Map1.txn_id
JOIN Products ON Map1.prd_id = Products.id
;

/* Try adding rouge entry to Map 2. Does not work */
INSERT INTO Map2 VALUES (5,6); /* oooops niether transaction or Product exists */
;

代码:-

  • 删除现有的 tables 如果它们存在(注意由于 FK 的顺序)
  • 创建交易和产品 tables.
  • 向交易和产品添加一些数据 tables。
  • 不使用外键创建 Map1 映射 table。
    • 即它们不是必需的。
  • 使用外键创建 Map2 映射 table。
  • 加载具有相同数据的映射 table。
  • 根据to/using映射提取数据table Map1产生结果1.
  • 根据to/usinf映射tableMap2提取数据产生结果2。
    • 与结果 1 相同。
  • 插入一个 rouge 行 t0 Map1,试图将 ID 为 5 的 non-existing 事务映射到 ID 为 6
  • 的 non-existing 产品
  • 像以前一样从 Map1 中提取数据
    • 相同,即 rouge(无用)行被忽略
  • 向 Map2 插入一个 rouge 行,由于外键约束而失败。

结果

随留言:-

INSERT INTO Map2 VALUES (5,6)
> FOREIGN KEY constraint failed
> Time: 0s