检索数据

本文档将介绍数据检索的基础知识以及如何对 Firebase 数据进行排序和过滤。

准备工作

在使用 Realtime Database 之前,您需要:

  • 注册 Unity 项目并将其配置为使用 Firebase。

    • 如果您的 Unity 项目已在使用 Firebase,那么该项目已经注册并已配置为使用 Firebase。

    • 如果您没有 Unity 项目,则可以下载示例应用

  • Firebase Unity SDK(具体而言是 FirebaseDatabase.unitypackage)添加到您的 Unity 项目中。

请注意,为了将 Firebase 添加到 Unity 项目,需要在 Firebase 控制台中和打开的 Unity 项目中执行若干任务(例如,从控制台下载 Firebase 配置文件,然后将配置文件移到 Unity 项目中)。

检索数据

您可以通过一次性调用 GetValueAsync() 或将某个事件附加到 FirebaseDatabase 引用来检索 Firebase 数据。事件监听器在数据进入初始状态时会被调用一次,以后每当数据发生更改时都会被再次调用。

获取 DatabaseReference

要从数据库读取数据,您需要一个 DatabaseReference 实例:

using Firebase;
using Firebase.Database;
using Firebase.Extensions.TaskExtension; // for ContinueWithOnMainThread

public class MyScript: MonoBehaviour {
  void Start() {
    // Get the root reference location of the database.
    DatabaseReference reference = FirebaseDatabase.DefaultInstance.RootReference;
  }
}

读取数据一次

您可以使用 GetValueAsync 方法一次性读取某个给定路径下全部内容的静态快照。任务结果将包括一个快照,其中包含该位置下的所有数据,包括子数据。如果该位置没有任何数据,则返回的快照为 null

    FirebaseDatabase.DefaultInstance
      .GetReference("Leaders")
      .GetValueAsync().ContinueWithOnMainThread(task => {
        if (task.IsFaulted) {
          // Handle the error...
        }
        else if (task.IsCompleted) {
          DataSnapshot snapshot = task.Result;
          // Do something with snapshot...
        }
      });

监听事件

您可以添加事件监听器来接收数据更改信息:

事件 典型用法
ValueChanged 读取并监听对路径中所有内容的更改。
ChildAdded 检索项列表,或监听项列表中是否添加了新项。 建议与 ChildChangedChildRemoved 配合使用以监控列表的更改。
ChildChanged 监听列表中的项是否发生了更改。与 ChildAddedChildRemoved 配合使用以监控列表的更改。
ChildRemoved 监听列表中是否有项被移除。与 ChildAddedChildChanged 配合使用以监控列表的更改。
ChildMoved 监听有序列表的项顺序是否有更改。 每当有 ChildChanged 事件导致项目的顺序发生更改(基于您当前的排序依据方法)时,就会触发 ChildMoved 事件。

ValueChanged 事件

您可以使用 ValueChanged 事件来接收有关某个给定路径下内容发生更改的信息。此事件在附加监听器时触发一次,以后会在每次数据(包括子节点数据)发生更改时再次触发。系统会向事件回调函数传递一个包含该位置中所有数据(包括子节点数据)的快照。如果该位置没有任何数据,则返回的快照为 null

以下示例演示了一款游戏如何从数据库中检索排行榜得分情况:

      FirebaseDatabase.DefaultInstance
        .GetReference("Leaders")
        .ValueChanged += HandleValueChanged;
    }

    void HandleValueChanged(object sender, ValueChangedEventArgs args) {
      if (args.DatabaseError != null) {
        Debug.LogError(args.DatabaseError.Message);
        return;
      }
      // Do something with the data in args.Snapshot
    }

ValueChangedEventArgs 包含一个 DataSnapshot,其中包含事件发生时数据库中指定位置的数据。调用快照中的 Value 可返回一个表示数据的 Dictionary<string, object>。如果该位置不存在任何数据,调用 Value 将返回 null

在此示例中,还检查了 args.DatabaseError 以查看读取操作是否已取消。例如,如果客户端没有权限从 Firebase 数据库位置读取数据,则可取消读取操作。DatabaseError 中的信息将指明操作失败的原因。

稍后您可以使用具有相同路径的任何 DatabaseReference 退订事件。DatabaseReference 实例具有临时性质,可以视为访问任何路径和查询的方式。

      FirebaseDatabase.DefaultInstance
        .GetReference("Leaders")
        .ValueChanged -= HandleValueChanged; // unsubscribe from ValueChanged.
    }

子节点事件

当因为某项操作(例如通过 Push() 方法添加新的子节点,或通过 UpdateChildrenAsync() 方法更新子节点)而使得某个节点的子节点发生更改时,就会触发子节点事件。同时使用所有这些事件有助于监听数据库中某个特定节点是否有更改。例如,一款游戏可以综合使用这些方法来监控游戏会话评论中的活动,如下所示:

      var ref = FirebaseDatabase.DefaultInstance
      .GetReference("GameSessionComments");

      ref.ChildAdded += HandleChildAdded;
      ref.ChildChanged += HandleChildChanged;
      ref.ChildRemoved += HandleChildRemoved;
      ref.ChildMoved += HandleChildMoved;
    }

    void HandleChildAdded(object sender, ChildChangedEventArgs args) {
      if (args.DatabaseError != null) {
        Debug.LogError(args.DatabaseError.Message);
        return;
      }
      // Do something with the data in args.Snapshot
    }

    void HandleChildChanged(object sender, ChildChangedEventArgs args) {
      if (args.DatabaseError != null) {
        Debug.LogError(args.DatabaseError.Message);
        return;
      }
      // Do something with the data in args.Snapshot
    }

    void HandleChildRemoved(object sender, ChildChangedEventArgs args) {
      if (args.DatabaseError != null) {
        Debug.LogError(args.DatabaseError.Message);
        return;
      }
      // Do something with the data in args.Snapshot
    }

    void HandleChildMoved(object sender, ChildChangedEventArgs args) {
      if (args.DatabaseError != null) {
        Debug.LogError(args.DatabaseError.Message);
        return;
      }
      // Do something with the data in args.Snapshot
    }

ChildAdded 事件通常用于在 Firebase 数据库中检索项目列表。每个现有的子节点都会引发一次 ChildAdded 事件,并且以后每次向指定的路径添加新的子节点时都会再次引发该事件。系统会向监听器传递一个包含新子节点数据的快照。

ChildChanged 事件会在每次修改子节点时触发。 这包括对子节点的后代所做的任何修改。它通常与 ChildAddedChildRemoved 事件结合使用,以响应对项目列表的更改。传递给事件监听器的快照中将包含子节点更新后的数据。

ChildRemoved 事件会在直接子节点被移除时触发。它通常与 ChildAddedChildChanged 回调函数结合使用。传递给事件回调函数的快照中将包含已移除的子节点的数据。

每当引发 ChildChanged 事件的子节点数据更新操作还会导致子节点重新排序时,系统就会触发 ChildMoved 事件。该事件用于已通过 OrderByChildOrderByValue 排序的数据。

排序和过滤数据

您可以使用 Realtime Database Query 类来检索按键、按值或按子项的值排序的数据。您还可以对排序后的结果进行过滤,从而得到特定数量的结果或一系列键或值。

将数据排序

如需检索经过排序的数据,请先指定一种排序依据方法,确定如何对结果排序:

方法 用法
OrderByChild() 按指定子键的值对结果排序。
OrderByKey() 按子键对结果排序。
OrderByValue() 按子值对结果排序。

您每次只能使用一种排序依据方法。在同一查询中多次调用排序依据方法会引发错误。

以下示例演示了如何订阅按得分排序的得分排行榜信息。

      FirebaseDatabase.DefaultInstance
        .GetReference("Leaders").OrderByChild("score")
        .ValueChanged += HandleValueChanged;
    }

    void HandleValueChanged(object sender, ValueChangedEventArgs args) {
      if (args.DatabaseError != null) {
        Debug.LogError(args.DatabaseError.Message);
        return;
      }
      // Do something with the data in args.Snapshot
    }

此示例定义了一个查询,如果将该查询与一个 valuechanged 事件监听器结合使用,就能使客户端数据与数据库中的排行榜同步,并按每个条目的得分进行排序。 如需详细了解如何高效地设计您的数据结构,请参阅设计数据库的数据结构

调用 OrderByChild() 方法可指定结果排序所依据的子键。在此示例中,系统将按每个子项中的 "score" 值对结果进行排序。如需详细了解如何对其他数据类型进行排序,请参阅如何对查询数据进行排序

过滤数据

如需过滤数据,您可以在构建查询时将某种限制方法或范围方法与排序依据方法结合使用。

方法 用法
LimitToFirst() 设置要返回的项数上限:从经过排序的结果列表开头算起。
LimitToLast() 设置要返回的项数上限:从经过排序的结果列表结尾算起。
StartAt() 返回大于或等于指定键或值的项,具体取决于所选的排序依据方法。
EndAt() 返回小于或等于指定键或值的项,具体取决于所选的排序依据方法。
EqualTo() 返回等于指定键或值的项,具体取决于所选的排序依据方法。

与排序依据方法不同,您可以结合使用多种限制或范围函数。 例如,您可以结合使用 StartAt()EndAt() 方法来将查询结果限制在指定的取值范围内。

即使查询仅存在一个匹配项时,该快照仍是一个仅包含单个项目的列表。

限制结果数

您可以使用 LimitToFirst()LimitToLast() 方法来设置当运行给定回调函数时要同步的子项数上限。例如,如果使用 LimitToFirst() 将数量上限设为 100,则起初最多只会收到 100 次 ChildAdded 回调。如果您在 Firebase 数据库中存储的项不到 100 个,则每个项均会触发一次 ChildAdded 回调函数。

随着项发生更改,对于落入查询范围的项,您将收到 ChildAdded 回调函数;对于不再属于查询范围的项,您将收到 ChildRemoved 回调函数,因此总数始终保持为 100。

例如,以下代码将返回排行榜中的最高分:

      FirebaseDatabase.DefaultInstance
        .GetReference("Leaders").OrderByChild("score").LimitToLast(1)
        .ValueChanged += HandleValueChanged;
    }

    void HandleValueChanged(object sender, ValueChangedEventArgs args) {
      if (args.DatabaseError != null) {
        Debug.LogError(args.DatabaseError.Message);
        return;
      }
      // Do something with the data in args.Snapshot
    }

按键或值过滤

您可以使用 StartAt()EndAt()EqualTo() 为查询选择任意起点、终点和等值点。这对于将数据分页或查找其子项具有特定值的项非常有用。

如何对查询数据进行排序

本部分将介绍如何通过 Query 类中的每种排序依据方法对数据进行排序。

OrderByChild

使用 OrderByChild() 时,包含指定子键的数据将按以下方式排序:

  1. 指定子项键的值为 null 的子项排在最前面。
  2. 接下来是指定子项键的值为 false 的子项。如果多个子项的值均为 false,则按照键以字典顺序对它们进行排序。
  3. 接下来是指定子项键的值为 true 的子项。如果多个子项的值均为 true,则按照键以字典顺序对它们进行排序。
  4. 接下来是值为数字的子项,按升序排序。如果指定子节点的多个子项具有相同的数字值,则按照键对它们进行排序。
  5. 值为字符串的子项排列在值为数字的子项后面,并按字典顺序以升序排列。如果指定子节点的多个子项具有相同的值,则按照键以字典顺序对它们进行排序。
  6. 值为对象的子项放在最后,并按照键以字典顺序排序(升序)。

OrderByKey

使用 OrderByKey() 对数据进行排序时,系统会按照键以升序返回数据。

  1. 键可以解析为 32 位整数的子项排在最前,按升序排列。
  2. 以字符串值作为键的子项紧随其后,按字典顺序以升序排列。

OrderByValue

使用 OrderByValue() 时,系统将按子项本身的值对其进行排序。排序标准与 OrderByChild() 中相同,但这里使用的是节点本身的值而非指定子键的值。