当 CursorLoader 的构造函数不支持时,如何在 CursorLoader 中使用连接查询

Posted

技术标签:

【中文标题】当 CursorLoader 的构造函数不支持时,如何在 CursorLoader 中使用连接查询【英文标题】:how to use join query in CursorLoader when its constructor does not support it 【发布时间】:2012-12-14 23:15:52 【问题描述】:

我有两个具有 1:n 关系的表,我正在使用内容提供程序和 cursorloader。

如何进行连接查询以使用游标加载器?我可以在内容提供程序中使用 rawSql 以某种方式破解它,但如何在游标加载器构造函数中完成它超出了我的范围。

非常感谢!

CursorLoader(上下文上下文, Uri uri, String[] 投影, String selection, String[] selectionArgs, String sortOrder)

Uri 只能指向一个表时如何查询连接

【问题讨论】:

【参考方案1】:

Uri 不指向任何表。它指向你想要指向的任何东西。

假设您的两个表是CustomerOrder。一位客户可能有很多订单。您想要执行查询以获取所有未完成的订单...但您想要加入一些您需要的与客户相关的列,例如客户的姓名。

让我们进一步假设您已经定义了 content://your.authority.goes.here/customercontent://your.authority.goes.here/order 来纯粹查询这些表。

你有两个选择:

    在您的/order Uri 上添加客户显示名称的连接。拥有另一个可用列可能不会破坏提供者的任何现有消费者(尽管测试总是一个好主意)。这就是ContactsContract 所做的——它在几乎所有表的所有查询中加入一些基本列,例如联系人姓名。

    创建content://your.authority.goes.here/orderWithCust,它执行与/order 相同的基本查询,但包含您的联接。在这种情况下,您可以让insert()update()delete() 抛出某种RuntimeException,以提醒您不要使用/orderWithCust 作为Uri 来修改数据。

最后,设计一个ContentProviderUri 系统类似于设计一个REST Web 服务的URL 系统。在这两种情况下,连接都必须在提供者/服务器端完成,因此您可能需要打破单表对一 URL 基线以提供一些有用的连接。

【讨论】:

我在上面阅读了您的答案,但在我的情况下,使用连接的全部目的是通过一个查询从多个相关表中检索列。在我从游标加载器获得的单个游标中,我需要来自 ContactsContract.Contact 表的 DISPLAY_NAME_PRIMARY,来自 ContactsContract.RawContact 表的 ACCOUNT_NAME 和来自 ContactsContract.DATA 表的 DATA1。现在我不明白单个 Uri 如何帮助我解决这个问题,因为如果我给 Uri指向 ContactsContract.RawContact 表,那么我将如何访问 ContactsContract.Contact 和 ContactsContract.Data 表的相关列 @AbhishekChauhan:嗯,在你的情况下,你没有写ContentProvider,这意味着你不能像你在问题中所说的那样“在内容提供程序中使用 rawSql 以某种方式破解它”。我的答案是针对正在编写ContentProvider 并因此可以在其中进行连接的人。在您的情况下,您必须自己从多个 Cursor 对象中手动“加入”您的数据,而 Loader 框架在这方面没有多大帮助。我想我会立即使用AsyncTask,然后使用ContentResolver 进行查询并将您的数据合并到doInBackground() ya ...谢谢我将使用asynctask并稍后合并...这个更适合我 @CommonsWare:您能否通过示例更详细地解释选项一。谢谢。【参考方案2】:

我找到了一个使用 ContentProvider 子类的解决方案。 假设您有表 tblA 和另一个表 tblB。我建议创建两个类“AContentProvider”和“BContentProvider”。最重要的是,确保两个表都设置在同一个数据库中。

解决方案的主要部分是在 ContentProvider 中覆盖 ContentProvider.query(),您将从 CursorLoader 调用 - URI 决定它是哪一个:

@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sort) 
    SQLiteQueryBuilder qb = new SQLiteQueryBuilder();

    qb.setTables(
            "tblA LEFT JOIN tblB"
                    + " ON ("
                    + "tblA.b_id"
                    + " = "
                    + "tblB.id"
                    + ")"
    );

    ...
    // Content of projection is set by CursorLoader
    // usually in an Activity that implements LoaderManager.LoaderCallbacks<>

    Cursor c = qb.query(
            database,
            projection,
            selection,
            selectionArgs,
            groupBy,
            having,
            orderBy
    );

    ...
    return c;

如您所见,JOIN 是在 setTables() 中完成的。使用 projection 您可以确保只显示您真正需要的列,最重要的是,您没有重复的列,例如两个表中的“id”:

final String[] projection = new String[] 
        "tblA.*",
        "tblB.columnThatOnlyBHas"
;

利用覆盖并尝试在子类中完成尽可能多的工作;例如:如果游标结果集发生变化,我所有重写的 query() 方法都会调用 setNotificationUri() 来通知 ContentResolver。:

c.setNotificationUri(getContext().getContentResolver(), uri);

【讨论】:

以上是关于当 CursorLoader 的构造函数不支持时,如何在 CursorLoader 中使用连接查询的主要内容,如果未能解决你的问题,请参考以下文章

自定义 CursorLoader 和支持 ListView 的 CursorAdapter 之间的数据不同步

即使应用程序处于非活动状态,CursorLoader 如何自动更新视图?

数据更改后 CursorLoader 不更新

为啥当类包含任何参数化构造函数时编译器不提供默认构造函数? [复制]

Android:CursorLoader 在非最顶层 Fragment 上崩溃

分页列表和 CursorLoader