Android:SQLite一对多设计
有人对如何使用 ContentProvider
为 SQLite
实现一对多映射有好的建议吗?如果您查看 Uri ContentProvider#insert(Uri, ContentValues)
,您可以看到它具有包含要插入的数据的 ContentValues
参数。问题是在当前的实现中 ContentValues
不支持 put(String, Object)
方法,并且类是最终的,因此我无法扩展它。为什么这是一个问题?这是我的设计:
我有两个处于一对多关系的表。为了在代码中表示这些,我有 2 个模型对象。第一个表示主记录,并且有一个字段,该字段是第二个对象实例的列表。现在,我在模型对象 #1 中有一个辅助方法,它返回当前对象生成的 ContentValues
。使用 ContentValues#put 重载方法填充原始字段很简单,但我对列表不走运。因此,目前由于我的第二个表行只是一个字符串值,所以我生成了一个逗号分隔的字符串,然后我将其重新解析为 ContentProvider#insert
内的 String[]。这感觉很恶心,所以也许有人可以暗示如何以更干净的方式完成它。
这是一些代码。首先来自模型类:
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 */}
这是 ContentProvider#insert
方法的精简版本
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();
}
}
Anyone has good advise on how to implement one-to-many mapping for SQLite
using ContentProvider
? If you look at Uri ContentProvider#insert(Uri, ContentValues)
you can see that it has ContentValues
param that contains data to insert. The problem is that in its current implementation ContentValues
does not support put(String, Object)
method and class is final so I cannot extend it. Why it is a problem? Here comes my design:
I have 2 tables which are in one-to-many relationship. To represent these in code I have 2 model objects. 1st represents the main record and has a field that is a list of 2nd object instances. Now I have a helper method in the model object #1 which returns ContentValues
generated off the current object. It's trivial to populate a primitive fields with ContentValues#put
overloaded methods but I'm out of luck for the list. So currently since my 2nd table row is just a single String value I generate a comma delimited String which then I reparse to String[] inside ContentProvider#insert
. That feels yucky, so maybe someone can hint how it can be done in cleaner fashion.
Here's some code. First from the model class:
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 */}
and here's slimmed down version of ContentProvider#insert
method
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();
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我认为您正在寻找一对多关系的错误一端。
例如,看一下
ContactsContract
内容提供程序。联系人可以有许多电子邮件地址、许多电话号码等。实现的方法是在“许多”端执行插入/更新/删除操作。要添加新电话号码,您需要插入新电话号码,并提供该电话号码所属联系人的 ID。如果您有一个没有内容提供程序的普通 SQLite 数据库,您也会执行相同的操作。关系数据库中的一对多关系是通过“多”方的表上的插入/更新/删除来实现的,每个操作都有一个返回“一”方的外键。
现在,从面向对象的角度来看,这并不理想。欢迎您创建 ORM 风格的包装对象(想想 Hibernate),它允许您从“一”侧操作子集合。然后,足够智能的集合类可以转身并同步“许多”表以进行匹配。然而,正确实施这些并不一定是微不足道的。
I think you're looking at the wrong end of the one-to-many relationship.
Take a look at the
ContactsContract
content provider, for example. Contacts can have many email addresses, many phone numbers, etc. The way that is accomplished is by doing inserts/updates/deletes on the "many" side. To add a new phone number, you insert a new phone number, providing an ID of the contact for whom the phone number pertains.You would do the same if you had a plain SQLite database with no content provider. One-to-many relationships in relational databases are achieved via inserts/updates/deletes on a table for the "many" side, each having a foreign key back to the "one" side.
Now, from an OO standpoint, this isn't ideal. You are welcome to create ORM-style wrapper objects (think Hibernate) that allow you to manipulate a collection of children from the "one" side. A sufficiently-intelligent collection class can then turn around and synchronize the "many" table to match. However, these aren't necessarily trivial to implement properly.
您可以使用 ContentProviderOperations 来实现此目的。
它们基本上是批量操作,能够反向引用为父行生成的标识符。
这个答案很好地解释了如何将 ContentProviderOperations 用于一对多设计:withValueBackReference 的语义是什么?
You can use
ContentProviderOperations
for this.They are basically bulk operations with the ability to back-reference to the identifiers generated for parent rows.
How
ContentProviderOperations
can be used for a one-to-many design is very well explained in this answer: What are the semantics of withValueBackReference?所以我要回答我自己的问题。我走在正确的轨道上,有两个表和两个模型对象。缺少的和让我困惑的是,我想在一次调用中通过 ContentProvider#insert 直接插入复杂的数据。这是错误的。
ContentProvider
应创建并维护这两个表,但应由ContentProvider#insert
的 Uri 参数决定使用哪个表。使用ContentResolver并向模型对象添加“addFoo”等方法非常方便。这种方法将采用 ContentResolver 参数,最后是插入复杂记录的序列:ContentProvider#insert
使用不同的 Uri 来插入子记录所以剩下的唯一问题是如何将上述代码封装在事务中?
So I'm going to answer my own question. I was on the right track with having two tables and two model objects. What was missing and what confused me was that I wanted directly insert complex data through
ContentProvider#insert
in a single call. This is wrong.ContentProvider
should create and maintain these two tables but decision on which table to use should be dictated by Uri parameter ofContentProvider#insert
. It is very convenient to use ContentResolver and add methods such as "addFoo" to the model object. Such method would take ContentResolver parameter and at the end here are the sequence to insert a complex record:ContentProvider#insert
and obtain record idContentProvider#insert
with different Uri to insert child recordsSo the only remaining question is how to envelope the above code in transaction?