Android: Sqlite One-to-muitas Design
-
18-09-2019 - |
Pergunta
Qualquer um tem um bom conselho sobre como implementar o mapeamento um para muitos SQLite
usando ContentProvider
? Se você olhar para Uri ContentProvider#insert(Uri, ContentValues)
você pode ver que tem ContentValues
param que contém dados para inserir. O problema é que em sua implementação atual ContentValues
não suporta put(String, Object)
O método e a classe são finais, então não posso estendê -lo. Por que é um problema? Aqui vem meu design:
Eu tenho 2 mesas que estão em um relacionamento um para muitos. Para representá -los no código, tenho 2 objetos de modelo. O 1º representa o registro principal e possui um campo que é uma lista de instâncias do segundo objeto. Agora eu tenho um método auxiliar no objeto Model #1, que retorna ContentValues
gerado fora do objeto atual. É trivial preencher um campo primitivo com ContentValues#put
Métodos sobrecarregados, mas estou sem sorte para a lista. Então, atualmente, já que minha segunda linha de tabela é apenas um valor de string, eu gero uma string delimitada de vírgula que então reparo em string [] dentro ContentProvider#insert
. Isso parece nojento, então talvez alguém possa sugerir como isso pode ser feito de maneira mais limpa.
Aqui está algum código. Primeiro da classe modelo:
public ContentValues toContentValues() {
ContentValues values = new ContentValues();
values.put(ITEM_ID, itemId);
values.put(NAME, name);
values.put(TYPES, concat(types));
return values;
}
private String concat(String[] values) { /* trivial */}
E aqui está a versão reduzida de ContentProvider#insert
método
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.beginTransaction();
try {
// populate types
String[] types = ((String)values.get(Offer.TYPES)).split("|");
// we no longer need it
values.remove(Offer.TYPES);
// first insert row into OFFERS
final long rowId = db.insert("offers", Offer.NAME, values);
if (rowId > 0 && types != null) {
// now insert all types for the row
for (String t : types) {
ContentValues type = new ContentValues(8);
type.put(Offer.OFFER_ID, rowId);
type.put(Offer.TYPE, t);
// insert values into second table
db.insert("types", Offer.TYPE, type);
}
}
db.setTransactionSuccessful();
return ContentUris.withAppendedId(Offer.CONTENT_URI, rowId);
} catch (Exception e) {
Log.e(TAG, "Failed to insert record", e);
} finally {
db.endTransaction();
}
}
Solução
Eu acho que você está olhando para o fim errado do relacionamento um para muitos.
Dê uma olhada no ContactsContract
Provedor de conteúdo, por exemplo. Os contatos podem ter muitos endereços de e -mail, muitos números de telefone, etc. A maneira como é realizada é fazer inserções/atualizações/exclusão no "muitos" lado. Para adicionar um novo número de telefone, você insere um novo número de telefone, fornecendo um ID do contato para quem o número de telefone pertence.
Você faria o mesmo se tivesse um banco de dados SQLite simples sem provedor de conteúdo. Relacionamentos individuais nos bancos de dados relacionais são alcançados por meio de inserções/atualizações/exclui em uma tabela para o lado "muitos", cada um com uma chave estrangeira de volta ao lado "One".
Agora, do ponto de vista do OO, isso não é o ideal. Você pode criar objetos Wrapper no estilo ORM (pense em Hibernate) que permitem manipular uma coleção de crianças do lado "One". Uma classe de coleta suficientemente inteligente pode mudar e sincronizar a tabela "muitos" para corresponder. No entanto, estes não são necessariamente triviais para implementar adequadamente.
Outras dicas
Você pode usar ContentProviderOperations
por esta.
São basicamente operações em massa com a capacidade de referência de volta aos identificadores gerados para linhas parentais.
Quão ContentProviderOperations
Pode ser usado para um design um para muitos é muito bem explicado nesta resposta: Quais são a semântica do WithValueBackReference?
Então, eu vou responder minha própria pergunta. Eu estava no caminho certo com duas tabelas e dois objetos modelo. O que estava faltando e o que me confundiu era que eu queria inserir diretamente dados complexos através ContentProvider#insert
em uma única chamada. Isto está errado. ContentProvider
deve criar e manter essas duas tabelas, mas a decisão sobre qual tabela usar deve ser ditada pelo parâmetro URI de ContentProvider#insert
. É muito conveniente usar o contentResolver e adicionar métodos como "addfoo" ao objeto Model. Esse método levaria o parâmetro contentResolver e, no final, aqui estão a sequência para inserir um registro complexo:
- Insira o registro dos pais através
ContentProvider#insert
e obtenha ID de registro - De acordo com cada criança, fornece ID de pai (chave anteriores) e use
ContentProvider#insert
Com diferentes URI para inserir registros infantis
Portanto, a única questão restante é como envelope o código acima na transação?