Android:SQLite一对多设计

发布于 2024-08-20 12:28:28 字数 2087 浏览 9 评论 0原文

有人对如何使用 ContentProviderSQLite 实现一对多映射有好的建议吗?如果您查看 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

假装不在乎 2024-08-27 12:28:28

我认为您正在寻找一对多关系的错误一端。

例如,看一下 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.

孤独陪着我 2024-08-27 12:28:28

您可以使用 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?

涫野音 2024-08-27 12:28:28

所以我要回答我自己的问题。我走在正确的轨道上,有两个表和两个模型对象。缺少的和让我困惑的是,我想在一次调用中通过 ContentProvider#insert 直接插入复杂的数据。这是错误的。 ContentProvider 应创建并维护这两个表,但应由 ContentProvider#insert 的 Uri 参数决定使用哪个表。使用ContentResolver并向模型对象添加“addFoo”等方法非常方便。这种方法将采用 ContentResolver 参数,最后是插入复杂记录的序列:

  1. 通过 ContentProvider#insert 插入父记录并获取记录 id
  2. 每个子项提供父 ID(外键)并使用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 of ContentProvider#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:

  1. Insert parent record through ContentProvider#insert and obtain record id
  2. Per each child provide parent ID (foregn key) and use ContentProvider#insert with different Uri to insert child records

So the only remaining question is how to envelope the above code in transaction?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文