Search code examples
foreign-keysandroid-sqliteandroid-roomdaocascade

Android Room - CASCADE in dependent table for DELETE and keep untouchable for INSERT operation?


I have two tables - users and conversations. Many users may participate in one conversation and one user may be a member of many conversations

@Entity(tableName = "users")
public class User {
    @NonNull
    @PrimaryKey
    @ColumnInfo(name = "userId")
    private String id;  
    ...
    //Getters and setters  
}  

@Entity(tableName = "conversations")
public class Conversation {
    @NonNull
    @PrimaryKey
    @ColumnInfo(name = "uuid")
    private String id;  
    ...
    //Getters and setters  
}

To track which user participate in which conversation I use third table:

@Entity(tableName = "user_conversation", indices = {@Index(value = {"userId", "conversationUuid"}, unique = true)},
    primaryKeys = {"userId", "conversationUuid"},
    foreignKeys = {
            @ForeignKey(onDelete = CASCADE, entity = User.class,         parentColumns = "userId", childColumns = "userId"),
            @ForeignKey(onDelete = CASCADE, entity = Conversation.class, parentColumns = "uuid",   childColumns = "conversationUuid")
    })
public class JoinUserConversation {
    @NonNull
    private String userId;

    @NonNull
    private String conversationUuid;
    ...
    //Getters and setters
}

Dao objects:

@Dao
public interface UsersDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insert(User user);
    ...
}

@Dao
public interface ConversationsDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    long insert(Conversation conversation);
    ....
}

When I delete a user or conversation from their tables I need to delete them from user_conversation too and I use onDelete = CASCADE for this purpose. But when I insert a user or conversation to their tables I do not need to delete them from user_conversation. But as I understand insert with @Insert(onConflict = OnConflictStrategy.REPLACE) will be performed by means of delete if inserting entity already exist in the table and corresponding user or conversation will be deleted from user_conversation table. How to keep user_conversation from changes on insert?


Solution

  • I believe that you could accomplish what you want by checking the id of the row to be inserted exists if so update if not then insert.

    • Note I tried using deferred = true along with @Transaction but there is no conflict due to the CASCADE doing what it is supposed to do.

    For example, for Users :-

    Add

    @Query("SELECT count() FROM users WHERE userId = :userId")
    int getUserCount(String userId);
    

    and

    @Update()
    int updateUser(User user);
    

    And then have a method, such as :-

    private void upsertUser(User u) {
        if (mWTDao.getUserCount(u.getId()) > 0) {
            mWTDao.updateUser(u);
        } else {
            mWTDao.insert(u);
        }
    }
    
    • mWTDao being where all the Dao's exist (same for the example)

    Example

    Consider the following extract (from testing code):-

        ......... other code
    
        upsertUser(new User("userid001","Fred"));
        upsertUser(new User("userid002","Mary"));
        upsertUser(new User("userid003","Jane"));
        upsertUser(new User("userid004","Tom"));
    
        mWTDao.insert( new Conversation("c001","SUBJECT A"));
        mWTDao.insert( new Conversation("c002","SUBJECT X"));
    
        mWTDao.insert(new JoinUserConversation("userid001","c001"));
        mWTDao.insert(new JoinUserConversation("userid003","c001"));
    
        mWTDao.insert(new JoinUserConversation("userid002","c002"));
        mWTDao.insert(new JoinUserConversation("userid004","c002"));
    
        logAllInfo("PRE");
    
        upsertUser(new User("userid001","Harry"));
        upsertUser(new User("userid002","Gertrude"));
        logAllInfo("POST");
    
    }
    
    private void upsertUser(User u) {
        if (mWTDao.getUserCount(u.getId()) > 0) {
            mWTDao.updateUser(u);
        } else {
            mWTDao.insert(u);
        }
    }
    
    
    private void logAllInfo(String tag) {
    
        Log.d("INFO" + tag,"Logging Conversations via Original Mapping Table ");
    
        for (JoinUserConversation juc: mWTDao.getAllJUC()) {
            Conversation c = mWTDao.getConversation(juc.getConversationUuid());
            User u = mWTDao.getUser(juc.getUserId());
            Log.d("INFO" + tag,"\tFor Conversation " + c.getTitle() + "ID is (" + c.getId() + ") User is " + u.getName() + " ID is (" + u.getId() + ")");
        }
    
        Log.d("MAPINFO" + tag,"Logging Users ");
    
        for (User u: mWTDao.getAllUsers()) {
            Log.d("USERINFO" +  tag,"\tID is " + u.getId() + " Name is " + u.getName());
        }
    
        Log.d("CONVINFO","Logging Conversations");
        for (Conversation c: mWTDao.getAllConversations()) {
            Log.d("CONVINFO" + tag,"\tID is " + c.getId() + " Ttitle is " + c.getTitle());
        }
    }
    
    • This adds 4 users (using the upsertUser method) and 2 conversations and some user_conversations
    • It displays the information
    • It then tries to add already existing users using the upsertUser method
    • It again displays the information, showing that the existing users have been updated.

    Result

    2019-09-18 08:14:19.214  D/INFOPRE: Logging Conversations via Original Mapping Table 
    2019-09-18 08:14:19.220  D/INFOPRE:     For Conversation SUBJECT AID is (c001) User is Fred ID is (userid001)
    2019-09-18 08:14:19.222  D/INFOPRE:     For Conversation SUBJECT AID is (c001) User is Jane ID is (userid003)
    2019-09-18 08:14:19.224  D/INFOPRE:     For Conversation SUBJECT XID is (c002) User is Mary ID is (userid002)
    2019-09-18 08:14:19.226  D/INFOPRE:     For Conversation SUBJECT XID is (c002) User is Tom ID is (userid004)
    2019-09-18 08:14:19.226  D/MAPINFOPRE: Logging Users 
    2019-09-18 08:14:19.227  D/USERINFOPRE:     ID is userid001 Name is Fred
    2019-09-18 08:14:19.227  D/USERINFOPRE:     ID is userid002 Name is Mary
    2019-09-18 08:14:19.227  D/USERINFOPRE:     ID is userid003 Name is Jane
    2019-09-18 08:14:19.227  D/USERINFOPRE:     ID is userid004 Name is Tom
    2019-09-18 08:14:19.227  D/CONVINFO: Logging Conversations
    2019-09-18 08:14:19.228  D/CONVINFOPRE:     ID is c001 Ttitle is SUBJECT A
    2019-09-18 08:14:19.228  D/CONVINFOPRE:     ID is c002 Ttitle is SUBJECT X
    
    
    
    2019-09-18 08:14:19.233  D/INFOPOST: Logging Conversations via Original Mapping Table 
    2019-09-18 08:14:19.236  D/INFOPOST:    For Conversation SUBJECT AID is (c001) User is Harry ID is (userid001)
    2019-09-18 08:14:19.239  D/INFOPOST:    For Conversation SUBJECT AID is (c001) User is Jane ID is (userid003)
    2019-09-18 08:14:19.241  D/INFOPOST:    For Conversation SUBJECT XID is (c002) User is Gertrude ID is (userid002)
    2019-09-18 08:14:19.243  D/INFOPOST:    For Conversation SUBJECT XID is (c002) User is Tom ID is (userid004)
    2019-09-18 08:14:19.243  D/MAPINFOPOST: Logging Users 
    2019-09-18 08:14:19.244  D/USERINFOPOST:    ID is userid001 Name is Harry
    2019-09-18 08:14:19.244  D/USERINFOPOST:    ID is userid002 Name is Gertrude
    2019-09-18 08:14:19.244  D/USERINFOPOST:    ID is userid003 Name is Jane
    2019-09-18 08:14:19.244  D/USERINFOPOST:    ID is userid004 Name is Tom
    2019-09-18 08:14:19.244  D/CONVINFO: Logging Conversations
    2019-09-18 08:14:19.245  D/CONVINFOPOST:    ID is c001 Ttitle is SUBJECT A
    2019-09-18 08:14:19.245  D/CONVINFOPOST:    ID is c002 Ttitle is SUBJECT X
    
    • As can be seen Foreign Key children remain but the details have been updated and there are no new unintended users.